SwiftUI Animations in the same view are displayed differently - animation

In the first section there is no problem, but in the second section there is a problem in my animation. using the same view in the first section and the second section.
In the second section, the text shifts to the right and left. But I don't want that. I want the text to remain stable just like in the first section.
SectionView:
(option == 1) = image on the left, text on the right.
(option == 2) = image on the right, text on the left.
struct SectionView: View {
var title: String
var imageView: AnyView
var description: String
var option: Int
var body: some View {
Group {
if option == 1 {
HStack {
ZStack {
Rectangle()
.frame(width: 125, height: 120)
.foregroundColor(Color(UIColor.secondarySystemBackground))
.cornerRadius(40, corners: [.topRight, .bottomRight])
imageView
}
Spacer()
VStack {
Text(title)
.font(.system(size: 14, weight: .bold, design: .rounded))
Text(description)
.multilineTextAlignment(.center)
.font(.system(size: 12, weight: .medium, design: .rounded))
.padding(.trailing, 5)
}
.frame(height: 120)
.foregroundColor(Color(UIColor.label))
}
} else if option == 2 {
HStack {
VStack {
Text(title)
.font(.system(size: 14, weight: .bold, design: .rounded))
Text(description)
.multilineTextAlignment(.center)
.font(.system(size: 12, weight: .medium, design: .rounded))
.padding(.leading, 5)
}
.frame(height: 120)
.foregroundColor(Color(UIColor.label))
Spacer()
ZStack {
Rectangle()
.frame(width: 125, height: 120)
.foregroundColor(Color(UIColor.secondarySystemBackground))
.cornerRadius(40, corners: [.topLeft, .bottomLeft])
imageView
}
}
}
}
}
}
MainViewModel:
I am adding sections here.
As you can see I'm using the same view in the two CategorySections I've added. In the first section, the animation is displayed properly, but while the animation is displayed in the second section, the text shifts to the right and left. What is the reason for this?
class MainViewModel: ObservableObject {
#Published var section: [CategorySection] = []
init() {
getSections()
}
func getSections() {
section = [
CategorySection(title: "Trafik Levhaları", imageView: AnyView(PoliceSignsSectionIconView()), description: "Trafik levhaları trafiğe çıkan her sürücünün mutlaka dikkat etmesi gereken uyarı işaretleridir. Trafik kurallarını ve gerekliliklerini uygulama açısından önemli bir yeri olan trafik işaretleri mutlaka izlenmeli. Bu bölümde trafik levha işaretlerinin anlamlarını öğrenebilirsiniz.", option: 1, page: TrafficSignsView()),
CategorySection(title: "Polis İşaretleri", imageView: AnyView(PoliceSignsSectionIconView()), description: "Trafik polisleri gerektiği durumlarda yolda trafiğin kontrolünü sağlar. Çoğu yerde karşımıza çıkabilecek bu durumda trafik polisi işaretlerinin anlamını bilmemiz gerekmektedir. Bu bölümde polis işaretlerinin anlamlarını öğrenebilirsiniz.", option: 2, page: PoliceSignsView()),
....
]
}
}
CategorySection Model:
struct CategorySection {
let id = UUID()
let title: String
let imageView: AnyView
let description: String
let option: Int
let page: Any
}
Using SectionView:
I am using the sections I added in MainViewModel here.
struct MainView: View {
#ObservedObject var mainViewModel: MainViewModel = MainViewModel()
#EnvironmentObject var publishObjects: PublishObjects
var body: some View {
NavigationView {
ScrollView(showsIndicators: false) {
VStack(spacing: 30) {
ForEach(mainViewModel.section, id: \.id) { section in
NavigationLink(
destination: AnyView(_fromValue: section.page)?.navigationBarTitleDisplayMode(.inline),
label: {
SectionView(title: section.title, imageView: section.imageView, description: section.description, option: section.option)
})
}
}
}
.navigationBarTitle("Ehliyet Sınavım")
.onAppear {
}
}
.accentColor(Color(UIColor.label))
}
}
PoliceSignsSectionIconView(Animation here):
I applied my animations on this page.
struct PoliceSignsSectionIconView: View {
#State var isDrawing: Bool = true
let pathBounds = UIBezierPath.calculateBounds(paths: [.policePath1, .policePath2, .policePath3, .policePath4, .policePath5, .policePath6, .policePath7, .policePath8, .policePath9, .policePath10])
var body: some View {
Text("Test Icon")
.frame(width: 75, height: 70)
.opacity(isDrawing ? 1 : 0)
.onAppear {
self.isDrawing.toggle()
}
.animation(.easeInOut(duration: 2).repeatForever(autoreverses: true))
}
}

I solved my problem. I don't know why my problem was solved when I added List object.
MainView:
struct MainView: View {
#ObservedObject var mainViewModel: MainViewModel = MainViewModel()
#EnvironmentObject var publishObjects: PublishObjects
var body: some View {
NavigationView {
List(mainViewModel.section, id: \.id) { section in
NavigationLink(
destination: AnyView(_fromValue: section.page)?.navigationBarTitleDisplayMode(.inline),
label: {
SectionView(title: section.title, imageView: section.imageView, description: section.description, sectionStatus: section.sectionStatus)
})
}
.navigationBarTitle("Ehliyet Sınavım")
}
.accentColor(Color(UIColor.label))
}
}

Related

An interesting animation with SwiftUI's LazyVGrid on multiple items insert and remove

I have a button where let the user see some more of the items on tap. Initially it's shows of 4 items. After tap, I add rest of the items to the list and for less, I just show the first 4 items. The default animation gets weird every time playing with it. It's overlapping, comes from bottom. For demonstration purpose, I have slow down the animations in simulator.
You can find a demonstration app's source code: https://github.com/nesimtunc/swiftui-playground
Basically this is the whole code.
What's wrong with my implementation? Why this default animation is like this? How can I implement one without side effects?
PS: I have already tried MatchedGeometry and it didn't helped.
Thank you!
import Foundation
import SwiftUI
class ItemModel<T>: NSObject, ObservableObject {
var items: [T]
let showMoreText: String
let showLessText: String
let visibleItemsCount: Int
#Published var visibleItems: [T] = []
#Published var showAll: Bool = false
#Published var toggleText: String = ""
init(
items: [T],
showMoreText: String,
showLessText: String,
visibleItemsCount: Int,
showAll: Bool = false
) {
self.items = items
self.showMoreText = showMoreText
self.showLessText = showLessText
self.visibleItemsCount = visibleItemsCount
self.showAll = showAll
visibleItems = showAll ? items : Array(items.prefix(visibleItemsCount))
toggleText = showAll ? showLessText : showMoreText
}
func toggle() {
showAll.toggle()
update()
}
private func update() {
visibleItems = showAll ? items : Array(items.prefix(visibleItemsCount))
toggleText = showAll ? showLessText : showMoreText
}
}
struct ContentView: View {
private var col = Array(repeating: GridItem(.flexible(), spacing: 16), count: 2)
private let visibleItemsCount = 4
private let spacing: CGFloat = 16.0
#ObservedObject private var model: ItemModel<Int>
init() {
var newItems = [Int]()
for i in 0..<10 {
newItems.append(i)
}
self.model = ItemModel(items: newItems,
showMoreText: "Show More",
showLessText: "Show Less",
visibleItemsCount: visibleItemsCount)
}
var body: some View {
ScrollView {
LazyVGrid(columns: col , alignment: .center, spacing: spacing) {
ForEach(model.visibleItems, id: \.self) { i in
Text("\(i)")
.frame(maxWidth: .infinity, minHeight: 100)
.font(.title)
.foregroundColor(Color.white)
.background(Rectangle().fill(Color.orange))
}
}
Button {
withAnimation {
model.toggle()
}
} label: {
Text(model.toggleText)
.fontWeight(.semibold)
.foregroundColor(Color.primary)
.frame(maxWidth: .infinity, minHeight: 50)
.background(Capsule().strokeBorder(Color.secondary, lineWidth: 1.5))
}
// This is on for demonstartion purpose, using same data but with whole
LazyVGrid(columns: col , alignment: .center, spacing: spacing) {
ForEach(model.items, id: \.self) { i in
Text("\(i)")
.frame(width: 100, height: 100, alignment: .center)
.font(.title)
.foregroundColor(Color.white)
.background(Rectangle().fill(Color.blue))
}
}
}.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
.environment(\.sizeCategory, .small)
.previewDevice("iPhone 13 Pro Max")
.previewLayout(.device)
}
}
}
LazyVGrid is lazy, its whole content size is not always ready to animate when putting inside a ScrollView. There's a conflict I guess. I would prefer to keep only one LazyVGrid.
var body: some View {
ScrollView {
LazyVGrid(columns: col , alignment: .center, spacing: spacing) {
Section(footer: showHideButton) {
ForEach(model.visibleItems, id: \.self) { i in
Text("\(i)")
.frame(maxWidth: .infinity, minHeight: 100)
.font(.title)
.foregroundColor(Color.white)
.background(Rectangle().fill(Color.orange))
}.transaction { $0.animation = nil } // --> this line can be removed
}
Section {
ForEach(20...30, id: \.self) { i in
Text("\(i)")
.frame(maxWidth: .infinity, minHeight: 100)
.font(.title)
.foregroundColor(Color.white)
.background(Rectangle().fill(Color.blue))
}
}
}
}.padding()
}
#ViewBuilder
var showHideButton: some View {
Button {
withAnimation {
model.toggle()
}
} label: {
Text(model.toggleText)
.fontWeight(.semibold)
.foregroundColor(Color.primary)
.frame(maxWidth: .infinity, minHeight: 50)
.background(Capsule().strokeBorder(Color.secondary, lineWidth: 1.5))
}
}
There's another thing called Transitions, please also take a look if you need more advance animations
https://www.objc.io/blog/2022/04/14/transitions/

Text gets truncated or hidden on a TextField on a macOS app

I have a TextField on a .sheet that is acting up and I don't know why or how to solve it. When you type a long text, the cursor reaches the end of the TextField (the right end), and it stays there. You can continue to write (and the text will get entered) but you can't see it, nor scroll to it, nor move the cursor.
Is this a default behavior? If not, how do I fix it? What am I not doing right? I haven't found anything either here at SO, or on Apple's forums or various SwiftUI sites.
Here's how it looks
And here's the code for the view:
struct SheetViewNew: View {
#EnvironmentObject private var taskdata: DataModel
#Binding var isVisible: Bool
#Binding var enteredText: String
#Binding var priority: Int
var priorities = [0, 1, 2]
var body: some View {
VStack {
Text("Enter a new task")
.font(.headline)
.multilineTextAlignment(.center)
TextField("New task", text: $enteredText)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.system(size: 14.0))
.onSubmit{
if enteredText == "" {
self.isVisible = false
return
}
taskdata.tasks.append(Task(id: UUID(), title: enteredText, priority: priority))
}
Picker("Priority", selection: $priority) {
Text("Today").tag(0)
Text("Important").tag(1)
Text("Normal").tag(2)
}.pickerStyle(RadioGroupPickerStyle())
Spacer()
HStack {
Button("Cancel") {
self.isVisible = false
self.enteredText = ""
}
.keyboardShortcut(.cancelAction)
.buttonStyle(DefaultButtonStyle())
Spacer()
Button("Add Task") {
if enteredText == "" {
self.isVisible = false
return
}
taskdata.tasks.append(Task(id: UUID(), title: enteredText, priority: priority))
taskdata.sortList()
self.isVisible = false
}
.keyboardShortcut(.defaultAction)
.buttonStyle(DefaultButtonStyle())
}
}
.frame(width: 300, height: 200)
.padding()
}
}

SwiftUI - How to animate components corresponding to array elements?

I have an HStack of circles in SwiftUI, and the number of circles is determined based on the length of an array, like this:
#State var myArr = [...]
...
ScrollView(.horizontal) {
HStack {
ForEach(myArr) { item in
Circle()
//.frame(...)
//.animation(...) I tried this, it didn't work
}
}
}
Then I have a button that appends an element to this array, effectively adding a circle to the view:
Button {
myArr.append(...)
} label: {
...
}
The button works as intended, however, the new circle that is added to the view appears very abruptly, and seems choppy. How can I animate this in any way? Perhaps it slides in from the side, or grows from a very small circle to its normal size.
You are missing transition, here is what you looking:
struct ContentView: View {
#State private var array: [Int] = Array(0...2)
var body: some View {
ScrollView(.horizontal) {
HStack {
ForEach(array, id:\.self) { item in
Circle()
.frame(width: 50, height: 50)
.transition(AnyTransition.scale)
}
}
}
.animation(.default, value: array.count)
Button("add new circle") {
array.append(array.count)
}
Button("remove a circle") {
if array.count > 0 {
array.remove(at: array.count - 1)
}
}
}
}
a version with automatic scroll to the last circle:
struct myItem: Identifiable, Equatable {
let id = UUID()
var size: CGFloat
}
struct ContentView: View {
#State private var myArr: [myItem] = [
myItem(size: 10),
myItem(size: 40),
myItem(size: 30)
]
var body: some View {
ScrollViewReader { scrollProxy in
VStack(alignment: .leading) {
Spacer()
ScrollView(.horizontal) {
HStack {
ForEach(myArr) { item in
Circle()
.id(item.id)
.frame(width: item.size, height: item.size)
.transition(.scale)
}
}
}
.animation(.easeInOut(duration: 1), value: myArr)
Spacer()
Button("Add One") {
let new = myItem(size: CGFloat.random(in: 10...100))
myArr.append(new)
}
.onChange(of: myArr) { _ in
withAnimation {
scrollProxy.scrollTo(myArr.last!.id, anchor: .trailing)
}
}
.frame(maxWidth: .infinity, alignment: .center)
}
.padding()
}
}
}

How do I create another unique view for each device?

How do I create another view when the user chooses a device ? For each device, the view will not be the same because the information given will not be the same ( custom view )
Thank you to put me on the track.
Canvas image
struct ContentView: View {
var body: some View {
NavigationView {
List(Ios, children: \.sousMenuIos) { item in
HStack {
Image(item.image)
.resizable()
.scaledToFit()
.frame(width: 60, height: 60)
Text(item.name)
.font(.system(.title3, design: .rounded))
.bold()
}
}
.navigationTitle("Jailbreak")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
You need to wrap your list label in a NavigationLink and pass the item to the costum view that you want. here is a working example with dummy data.
struct ListItems: View {
var body: some View {
NavigationView {
List(iOS.previewData) { item in
NavigationLink(destination: CostumView(text: item.name)){
HStack {
Image(systemName: item.image)
.resizable()
.scaledToFit()
.frame(width: 60, height: 60)
Text(item.name)
.font(.system(.title3, design: .rounded))
.bold()
}
}
}
.navigationTitle("Jailbreak")
}
}
}
struct CostumView: View {
var text: String
var body: some View{
Text(text)
}
}
struct ListItems_Previews: PreviewProvider {
static var previews: some View {
ListItems()
}
}
struct iOS: Identifiable {
var id = UUID()
var image: String = ""
var name: String
static let previewData = [
iOS(image: "phone", name: "iPhone 8"),
iOS(image: "phone", name: "iPhone X")
]
}

MacOS-style List with Plus and Minus buttons

How can I do something like that in SwiftUI?
Reckon this view is built in Cocoa, since I can't even layout List and GroupBox properly: strange border appears.
There is no Table view in SwiftUI, there is no NSSegmentedControl.
This is the code I got so far:
import SwiftUI
struct DetailView: View {
let text: String
var body: some View {
GroupBox {
Text(text)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.padding(.leading, 20)
.frame(width:300, height:300)
}
}
struct ContentView: View {
private let names = ["One", "Two", "Three"]
#State private var selection: String? = "One"
var body: some View {
NavigationView {
List(selection: $selection) {
Section(header:
Text("Header")) {
ForEach(names, id: \.self) { name in
NavigationLink(destination: DetailView(text: name)) {
Text(name)
}
}
}
}.frame(width: 200, height: 300).padding(10).border(Color.green, width: 0)
DetailView(text: self.selection ?? "none selected")
}.padding(10)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import SwiftUI
import AppKit
struct ContentView: View {
var body: some View {
HStack(spacing: 20) {
PrinterPicker()
.frame(width: 160)
PrinterDetail()
.frame(width: 320)
} //
.padding()
}
}
struct PrinterPicker: View {
var body: some View {
VStack(spacing: 0) {
PrinterList()
PrinterListToolbar()
} //
.border(Color(NSColor.gridColor), width: 1)
}
}
struct PrinterList: View {
var body: some View {
List {
Text("Printer 1")
.font(Font.system(size: 15))
Text("Printer 2")
.font(Font.system(size: 15))
}
}
}
struct PrinterListToolbar: View {
var body: some View {
HStack(spacing: 0) {
ListButton(imageName: NSImage.addTemplateName)
Divider()
ListButton(imageName: NSImage.removeTemplateName)
Divider()
Spacer()
} //
.frame(height: 20)
}
}
struct ListButton: View {
var imageName: String
var body: some View {
Button(action: {}) {
Image(nsImage: NSImage(named: imageName)!)
.resizable()
} //
.buttonStyle(BorderlessButtonStyle())
.frame(width: 20, height: 20)
}
}
struct PrinterDetail: View {
var body: some View {
HStack {
Spacer()
VStack {
Spacer()
Text("No printers are available.")
Text("Click Add (+) to set up a printer.")
Spacer()
}
Spacer()
} //
.font(Font.system(size: 15))
.background(Color(
NSColor.unemphasizedSelectedContentBackgroundColor))
.cornerRadius(6)
.overlay(
RoundedRectangle(cornerRadius: 6)
.stroke(lineWidth: 1)
.foregroundColor(Color(NSColor.gridColor)))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Resources