SwiftUI animation issue - animation
I'm creating a flashcard view for language education. The first iteration consisted of flipping cards to front and back. The second iteration consisted of an addition of a swipe-based interface. The third iteration was meant to combine the previous two; however I'm having animation visualization issues (i.e. the third flip animation doesn't look like the first iteration).
first and third iteration gif displaying issue
first iteration code:
struct CardBack : View {
let width : CGFloat
let height : CGFloat
let firLanguage: String
let wordClass: String
let accuracy: String
let definition: String
#Binding var degree : Double
var body: some View {
ZStack {
VStack {
HStack {
Text(wordClass)
.padding(10)
.background(Color.gray.opacity(0.1))
.cornerRadius(10)
.padding(.horizontal, 5)
.padding(.top, 5)
.font(.system(size: 15))
Text(firLanguage)
.padding(10)
.background(Color.gray.opacity(0.1))
.cornerRadius(10)
.padding(.trailing, 5)
.padding(.top, 5)
.font(.system(size: 15))
Text(accuracy)
.padding(10)
.background(Color.gray.opacity(0.1))
.cornerRadius(10)
.padding(.trailing, 5)
.padding(.top, 5)
.font(.system(size: 15))
Spacer()
Image(systemName: "command.circle.fill")
.font(.system(size: 29))
.padding(.horizontal, 5)
.padding(.top, 5)
.foregroundColor(Color.gray)
}
Spacer()
HStack {
Text("word analysis subview")
.padding(10)
.background(Color.gray.opacity(0.1))
.cornerRadius(10)
.padding(.horizontal, 5)
.padding(.bottom, 5)
.font(.system(size: 15))
Spacer()
}
}
.frame(width: 350, height: 190)
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 5)
VStack {
Spacer()
Text(definition)
.font(.title)
Spacer()
}
.frame(width: 350, height: 90)
.background(Color.white)
.cornerRadius(0)
.shadow(radius: 1)
}
.rotation3DEffect(Angle(degrees: degree), axis: (x: 1, y: 0, z: 0))
}
}
struct CardFront : View {
let width : CGFloat
let height : CGFloat
let secLanguage: String
let wordClass: String
let accuracy: String
let term: String
#Binding var degree : Double
var body: some View {
ZStack {
VStack {
HStack {
Text(wordClass)
.padding(10)
.background(Color.gray.opacity(0.1))
.cornerRadius(10)
.padding(.horizontal, 5)
.padding(.top, 5)
.font(.system(size: 15))
Text(secLanguage)
.padding(10)
.background(Color.gray.opacity(0.1))
.cornerRadius(10)
.padding(.trailing, 5)
.padding(.top, 5)
.font(.system(size: 15))
Text(accuracy)
.padding(10)
.background(Color.gray.opacity(0.1))
.cornerRadius(10)
.padding(.trailing, 5)
.padding(.top, 5)
.font(.system(size: 15))
Spacer()
Image(systemName: "command.circle.fill")
.font(.system(size: 29))
.padding(.horizontal, 5)
.padding(.top, 5)
.foregroundColor(Color.gray)
}
Spacer()
HStack {
Text("word analysis subview")
.padding(10)
.background(Color.gray.opacity(0.1))
.cornerRadius(10)
.padding(.horizontal, 5)
.padding(.bottom, 5)
.font(.system(size: 15))
Spacer()
}
}
.frame(width: 350, height: 190)
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 5)
VStack {
Spacer()
Text(term)
.font(.title)
Spacer()
}
.frame(width: 350, height: 90)
.background(Color.white)
.cornerRadius(0)
.shadow(radius: 1)
}
.rotation3DEffect(Angle(degrees: degree), axis: (x: 1, y: 0, z: 0))
}
}
struct CardFlipView: View {
//MARK: Variables
#State var backDegree = 0.0
#State var frontDegree = -90.0
#State var isFlipped = true
let width : CGFloat = 200
let height : CGFloat = 250
let durationAndDelay : CGFloat = 0.3
let firLanguage: String = "English"
let secLanguage: String = "Spanish"
let wordClass: String = "Verb"
let accuracy: String = "63%"
let term: String = "hablar"
let definition: String = "to talk"
//MARK: Flip Card Function
func flipCard () {
isFlipped = !isFlipped
if isFlipped {
withAnimation(.linear(duration: durationAndDelay)) {
backDegree = 90
}
withAnimation(.linear(duration: durationAndDelay).delay(durationAndDelay)){
frontDegree = 0
}
} else {
withAnimation(.linear(duration: durationAndDelay)) {
frontDegree = -90
}
withAnimation(.linear(duration: durationAndDelay).delay(durationAndDelay)){
backDegree = 0
}
}
}
//MARK: View Body
var body: some View {
ZStack {
CardBack(width: width, height: height, firLanguage: firLanguage, wordClass: wordClass, accuracy: accuracy, definition: definition, degree: $frontDegree)
CardFront(width: width, height: height, secLanguage: secLanguage, wordClass: wordClass, accuracy: accuracy, term: term, degree: $backDegree)
}.onTapGesture {
flipCard ()
}
}
}
third iteration code:
struct Word: Hashable, CustomStringConvertible {
var id: Int
let front: String
let back: String
let term: String
let definition: String
let accuracy: Int
let wordclass: String
var description: String {
return "\(term), id: \(id)"
}
}
struct ContentView3: View {
/// List of words
#State var words: [Word] = [
Word(id: 0, front: "Spanish", back: "English", term: "to speak", definition: "hablar", accuracy: 63, wordclass: "Verb"),
Word(id: 1, front: "Spanish", back: "English", term: "to eat", definition: "comer", accuracy: 63, wordclass: "Verb"),
//Word(id: 2, front: "Spanish", back: "English", term: "to think", definition: "pensar", accuracy: 63, wordclass: "Verb"),
//Word(id: 3, front: "Spanish", back: "English", term: "to live", definition: "vivir", accuracy: 63, wordclass: "Verb"),
//Word(id: 4, front: "Spanish", back: "English", term: "to write", definition: "escribir", accuracy: 63, wordclass: "Verb"),
]
/// Return the CardViews width for the given offset in the array
/// - Parameters:
/// - geometry: The geometry proxy of the parent
/// - id: The ID of the current user
private func getCardWidth(_ geometry: GeometryProxy, id: Int) -> CGFloat {
let offset: CGFloat = CGFloat(words.count - 1 - id) * 10
return geometry.size.width - offset
}
/// Return the CardViews frame offset for the given offset in the array
/// - Parameters:
/// - geometry: The geometry proxy of the parent
/// - id: The ID of the current user
private func getCardOffset(_ geometry: GeometryProxy, id: Int) -> CGFloat {
return CGFloat(words.count - 1 - id) * 10
}
private var maxID: Int {
return self.words.map { $0.id }.max() ?? 0
}
var body: some View {
VStack {
GeometryReader { geometry in
LinearGradient(gradient: Gradient(colors: [Color.init(#colorLiteral(red: 0.9686274529, green: 0.78039217, blue: 0.3450980484, alpha: 1)), Color.init(#colorLiteral(red: 1, green: 0.9882352941, blue: 0.862745098, alpha: 1))]), startPoint: .bottom, endPoint: .top)
.frame(width: geometry.size.width * 1.5, height: geometry.size.height)
.background(Color.yellow)
.clipShape(Circle())
.offset(x: -geometry.size.width / 4, y: -geometry.size.height / 6)
VStack(spacing: 24) {
Spacer()
ZStack {
ForEach(self.words, id: \.self) { word in
Group {
// Range Operator
if (self.maxID - 3)...self.maxID ~= word.id {
CardView(word: word, onRemove: { removedWord in
// Remove that user from our array
self.words.removeAll { $0.id == removedWord.id }
})
.animation(.spring())
.frame(width: self.getCardWidth(geometry, id: word.id), height: 400)
.offset(x: 0, y: self.getCardOffset(geometry, id: word.id))
}
}
}
}
Spacer()
}
}
}.padding()
}
}
//MARK: CardView
struct CardView: View {
#State private var translation: CGSize = .zero
#State private var swipeStatus: LikeDislike = .none
#State var backDegree = 0.0
#State var frontDegree = -90.0
#State var isFlipped = true
let durationAndDelay : CGFloat = 0.3
var word: Word
private var onRemove: (_ word: Word) -> Void
private var thresholdPercentage: CGFloat = 0.5 // when the user has draged 50% the width of the screen in either direction
private enum LikeDislike: Int {
case like, dislike, none
}
init(word: Word, onRemove: #escaping (_ word: Word) -> Void) {
self.word = word
self.onRemove = onRemove
}
private func getGesturePercentage(_ geometry: GeometryProxy, from gesture: DragGesture.Value) -> CGFloat {
gesture.translation.width / geometry.size.width
}
func flipCard () {
isFlipped = !isFlipped
if isFlipped {
withAnimation(.linear(duration: durationAndDelay)) {
backDegree = 90
}
withAnimation(.linear(duration: durationAndDelay).delay(durationAndDelay)){
frontDegree = 0
}
} else {
withAnimation(.linear(duration: durationAndDelay)) {
frontDegree = -90
}
withAnimation(.linear(duration: durationAndDelay).delay(durationAndDelay)){
backDegree = 0
}
}
}
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
ZStack(alignment: self.swipeStatus == .like ? .topLeading : .topTrailing) {
if self.swipeStatus == .like {
Text("YES")
.font(.headline)
.padding()
.cornerRadius(10)
.foregroundColor(Color.green)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.green, lineWidth: 3.0)
).padding(24)
.rotationEffect(Angle.degrees(-45))
} else if self.swipeStatus == .dislike {
Text("NO")
.font(.headline)
.padding()
.cornerRadius(10)
.foregroundColor(Color.red)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.red, lineWidth: 3.0)
).padding(.top, 45)
.rotationEffect(Angle.degrees(45))
}
ZStack {
CardFront2(word: word, degree: $frontDegree)
CardBack2(word: word, degree: $backDegree)
}.onTapGesture {
flipCard ()
}
}
}
.animation(.interactiveSpring())
.offset(x: self.translation.width, y: 0)
.rotationEffect(.degrees(Double(self.translation.width / geometry.size.width) * 1), anchor: .bottom)
.gesture(
DragGesture()
.onChanged { value in
self.translation = value.translation
if (self.getGesturePercentage(geometry, from: value)) >= self.thresholdPercentage {
self.swipeStatus = .like
} else if self.getGesturePercentage(geometry, from: value) <= -self.thresholdPercentage {
self.swipeStatus = .dislike
} else {
self.swipeStatus = .none
}
}.onEnded { value in
// determine snap distance > 0.5 (half the width of the screen)
if abs(self.getGesturePercentage(geometry, from: value)) > self.thresholdPercentage {
self.onRemove(self.word)
} else {
self.translation = .zero
}
}
)
}
}
}
struct CardFront2 : View {
var word: Word
#Binding var degree : Double
var body: some View {
ZStack {
HStack {
VStack(alignment: .leading, spacing: 6) {
HStack {
Text("\(self.word.front)") ///add toggleability
.font(.subheadline)
.foregroundColor(.gray)
.padding(7)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray, lineWidth: 1))
.cornerRadius(10)
Text("\(self.word.wordclass)") ///add toggleability
.font(.subheadline)
.foregroundColor(.gray)
.padding(7)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray, lineWidth: 1))
.cornerRadius(10)
Text("\(self.word.accuracy)%") ///add toggleability
.font(.subheadline)
.foregroundColor(.gray)
.padding(7)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray, lineWidth: 1))
.cornerRadius(10)
Spacer()
Image(systemName: "info.circle")
.foregroundColor(.gray)
}.padding(.top, 15)
HStack {
Spacer()
Text("\(self.word.definition)")
.font(.title)
.bold()
Spacer()
}.padding(.vertical, 15)
}
Spacer()
}
.padding(.horizontal, 10)
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 5)
}
.rotation3DEffect(Angle(degrees: degree), axis: (x: 0, y: 1, z: 0))
}
}
struct CardBack2 : View {
var word: Word
#Binding var degree : Double
var body: some View {
ZStack {
HStack {
VStack(alignment: .leading, spacing: 6) {
HStack {
Text("\(self.word.back)") ///add toggleability
.font(.subheadline)
.foregroundColor(.gray)
.padding(7)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray, lineWidth: 1))
.cornerRadius(10)
Text("\(self.word.wordclass)") ///add toggleability
.font(.subheadline)
.foregroundColor(.gray)
.padding(7)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray, lineWidth: 1))
.cornerRadius(10)
Text("\(self.word.accuracy)%") ///add toggleability
.font(.subheadline)
.foregroundColor(.gray)
.padding(7)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray, lineWidth: 1))
.cornerRadius(10)
Spacer()
Image(systemName: "info.circle")
.foregroundColor(.gray)
}.padding(.top, 15)
HStack {
Spacer()
Text("\(self.word.term)")
.font(.title)
.bold()
Spacer()
}.padding(.vertical, 15)
}
Spacer()
}
.padding(.horizontal, 10)
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 5)
}
.rotation3DEffect(Angle(degrees: degree), axis: (x: 0, y: 1, z: 0))
}
}
I tried moving the .onTapGesture onto different parent views. I have really tried playing with all things animation for a few hours now, and I haven't been able to crack it. I want the card flip on iteration 3 to mirror that of iteration 1.
To update you guys on my complete fix.
At first I was clueless, then I identified the GeometryReader{} (GR) under CardView() as potentially the issue; however, it was actually the unnecessary VStack{} directly under the GR that it was reading from, instead of the ZStack{}. Outlined below.
GeometryReader { geometry in
VStack { // <-REMOVED
ZStack(alignment: self.swipeStatus == .yes ? .topLeading : .topTrailing) {
if self.swipeStatus == .yes {...} else if self.swipeStatus == .no {...}
ZStack {
CardFront2(word: word, degree: $frontDegree)
CardBack2(word: word, degree: $backDegree)
}.onTapGesture {
flipCard ()
}
}
After removing the above outlined code I was able to see more of the animation take place; however it still wasn't complete. I identified an animation in the ContentView() causing it. Outlined below.
ZStack {
ForEach(self.words, id: \.self) { word in
Group {
if (self.maxID - 3)...self.maxID ~= word.id {
CardView2(word: word, onRemove: { removedWord in
self.words.removeAll { $0.id == removedWord.id }
})
.animation(.spring()) // <-REMOVED
.frame(width: self.getCardWidth(geometry, id: word.id), height: 400)
.offset(x: 0, y: self.getCardOffset(geometry, id: word.id))
}
}
}
}
I'm quite self-taught, so I consistently need shoving in the right direction. Thank you both for your pointers!
Related
SwiftUI Animation - Issue with Tabview
I am having an issue with animations in a tabview. I have a tabview with 2 views. The first view has a shape with an animation. The second view is a simple text. When I launch the application, View1 appears and the animation is correct. When I swipe to View2 and come back to View1, the animation no longer appear as intended and is somewhat random. Anyone might know what the issue might be ? Thank you. ContentView import SwiftUI struct ContentView: View { var body: some View { TabView { View1() View2() } //: TAB .tabViewStyle(PageTabViewStyle()) .padding(.vertical, 20) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } View1 import SwiftUI struct FollowEffect: GeometryEffect { var pct: CGFloat = 0 let path: Path var rotate = true var animatableData: CGFloat { get { return pct } set { pct = newValue } } func effectValue(size: CGSize) -> ProjectionTransform { if !rotate { let pt = percentPoint(pct) return ProjectionTransform(CGAffineTransform(translationX: pt.x, y: pt.y)) } else { // Calculate rotation angle, by calculating an imaginary line between two points // in the path: the current position (1) and a point very close behind in the path (2). let pt1 = percentPoint(pct) let pt2 = percentPoint(pct - 0.01) let a = pt2.x - pt1.x let b = pt2.y - pt1.y let angle = a < 0 ? atan(Double(b / a)) : atan(Double(b / a)) - Double.pi let transform = CGAffineTransform(translationX: pt1.x, y: pt1.y).rotated(by: CGFloat(angle)) return ProjectionTransform(transform) } } func percentPoint(_ percent: CGFloat) -> CGPoint { let pct = percent > 1 ? 0 : (percent < 0 ? 1 : percent) let f = pct > 0.999 ? CGFloat(1-0.001) : pct let t = pct > 0.999 ? CGFloat(1) : pct + 0.001 let tp = path.trimmedPath(from: f, to: t) return CGPoint(x: tp.boundingRect.midX, y: tp.boundingRect.midY) } } struct Solar2Grid: Shape { func path(in rect: CGRect) -> Path { return Solar2Grid.createArcPath(in: rect) } static func createArcPath(in rect: CGRect) -> Path { var path = Path() path.move(to: CGPoint(x: rect.width, y: 0)) path.addLine(to: CGPoint(x: rect.width, y: rect.height - 20)) path.addArc(center: CGPoint(x: rect.width - 20, y: rect.height - 20), radius: CGFloat(20), startAngle: .degrees(0), endAngle: .degrees(90), clockwise: false) path.addLine(to: CGPoint(x: 0, y: rect.height)) return path } } struct AnimRecView: View { #State var flag: Bool = false var body: some View { ZStack { Solar2Grid() .stroke(Color.purple, style: StrokeStyle( lineWidth: 2, dash: [3])) Circle() .foregroundColor(Color.red) .blur(radius: 3.0) .frame(width: 8, height: 8).offset(x: -40, y: -40) .modifier(FollowEffect(pct: self.flag ? 1 :0, path: Solar2Grid.createArcPath(in: CGRect(x: 0, y: 0, width: 80, height: 80)), rotate: false)) .onAppear { withAnimation(Animation.linear(duration: 1.5).repeatForever(autoreverses: false)) { self.flag.toggle() } } } } } struct View1: View { #State var flag: Bool = false var body: some View { VStack() { Text("View1") Spacer() HStack() { AnimRecView() } .frame(width: 80, height: 80, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/) Spacer() } .frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/) .background(LinearGradient(gradient: Gradient(colors: [Color.blue, Color.black]), startPoint: .top, endPoint: .bottom)) .cornerRadius(20) .padding(.horizontal, 20) } } struct View1_Previews: PreviewProvider { static var previews: some View { View1() } } View2 import SwiftUI struct View2: View { var body: some View { Text("View2") } } struct View2_Previews: PreviewProvider { static var previews: some View { View2() } }
The problem is that .onAppear() is only called once, so the next time the view is shown, the animation doesn't know what to do. The fix is to put an explicit animation on the Circle() itself. Then, when the view comes back on screen, it has the appropriate animation. Like this: struct AnimRecView: View { #State var flag: Bool = false var body: some View { ZStack { Solar2Grid() .stroke(Color.purple, style: StrokeStyle( lineWidth: 2, dash: [3])) Circle() .foregroundColor(Color.red) .blur(radius: 3.0) .frame(width: 8, height: 8).offset(x: -40, y: -40) .modifier(FollowEffect(pct: self.flag ? 1 : 0, path: Solar2Grid.createArcPath(in: CGRect(x: 0, y: 0, width: 80, height: 80)), rotate: false)) // Put the explicit animation here .animation(Animation.linear(duration: 1.5).repeatForever(autoreverses: false), value: flag) .onAppear { self.flag = true } } } }
Compiler Error: Cannot find variable in scope Xcode Bug
I keep getting a compiler error that's not allowing my app to build. Below is where I declared the variable showLogOutChoice. struct HomeView: View { #EnvironmentObject var userInfo: UserInfo var showLogOutChoice = false I then tried to use the value as a trigger for the actionsheet, and it's the compiler is throwing the error that it's not in scope. .toolbar { ToolbarItem(placement:.navigationBarLeading){ Button { } label: { VStack{ Image("CombIcon") .resizable() .frame(width: 40, height: 40) Text("Menu") } } } ToolbarItem(placement:.navigationBarTrailing){ Button { showLogOutChoice = true } label: { Text("Log Out ") } .confirmationDialog(Text:"Are You Sure You Want To Log Out?", isPresented: $showLogOutChoice , titleVisibility: .visible) { FBAuth.logout(completion: <#T##(Result<Bool, Error>) -> Void#>) } } } } } The variable should still be in scope, and it's not having the same issue when I change the variable to true. So I'm confused. I tried cleaning the build folder and deleting the derived data but still nothing has changed any suggestions. I'll copy the full file and leave it below as well for reference. PLEASE HELP! struct HomeView: View { #EnvironmentObject var userInfo: UserInfo var showLogOutChoice = false //TO DO connect picture to display if picture is not found display person fill //TO DO add scroll view var body: some View { let screenSize: CGRect = UIScreen.main.bounds let screenHeight = screenSize.height let screenWidth = screenSize.width NavigationView{ VStack{ VStack{ if (userInfo.user.profilePictureUrl == "") { Image( systemName: "person.fill") .font(.system(size: 120)) .padding() .foregroundColor(.black) } else{ WebImage(url: URL(string: userInfo.user.profilePictureUrl)) .resizable() .scaledToFill() .frame(width: 120, height: 120) .clipped() .cornerRadius(120) } } .overlay(RoundedRectangle(cornerRadius: 120) .stroke(Color.black, lineWidth: 1)) VStack{ Text("Hello \(userInfo.user.firstName)") .font(.custom("coolvetica", size: 55)) } VStack(alignment: .leading){ VStack(alignment:.leading){ Text("Your Upcoming Appointment") .font(.custom("coolvetica", size: 30)) } ZStack{ RoundedRectangle(cornerRadius: 13, style: .continuous) .frame(width: screenWidth * 0.9, height: screenHeight * 0.1) .foregroundColor(.white) .shadow(color: Color.black.opacity(0.3), radius: 3, x: 0, y: 6) Text("Holy Grail Appointment - Nov. 19th, 2021 8:00 AM") .font(.custom("coolvetica", size: 18)) }.padding(.bottom) HStack{ Button { //TO DO book an appointment action } label: { Text("Book An Appointment") .font(.custom("coolvetica", size: 20)) .frame(maxWidth: screenWidth * 0.8, minHeight: 20 ) .padding() .foregroundColor(.white) .background(Color("steelteal")) .cornerRadius(42) } // Button { // //TO DO reschedule an appointment action // // may just make it able to only book an appointment for now // } label: { // Text("Reschedule Appointment") // .font(.custom("coolvetica", size: 15)) // .padding() // .foregroundColor(.white) // .background(Color("steelteal")) // .cornerRadius(42) // // } } VStack{ Text("My Hair Journey W/ Shei") .font(.custom("coolvetica", size: 30)) } HStack{ // need to create logic to save pictures and a note to a specific user // create checkin page where pictures can be added the date and a note // create display screen that will pop out with full information once photo is click // if no checkins/updates are found then display text, no updates found would you like to add one with button // allow shei to also add updates to individual users Image( systemName: "person.fill") .font(.system(size: 80)) .padding() .foregroundColor(.black) .overlay(RoundedRectangle(cornerRadius: 2) .stroke(Color.black, lineWidth: 1)) Image( systemName: "person.fill") .font(.system(size: 80)) .padding() .foregroundColor(.black) .overlay(RoundedRectangle(cornerRadius: 2) .stroke(Color.black, lineWidth: 1)) Image( systemName: "person.fill") .font(.system(size: 80)) .padding() .foregroundColor(.black) .overlay(RoundedRectangle(cornerRadius: 2) .stroke(Color.black, lineWidth: 1)) } } } .toolbar { ToolbarItem(placement:.navigationBarLeading){ Button { } label: { VStack{ Image("CombIcon") .resizable() .frame(width: 40, height: 40) Text("Menu") } } } ToolbarItem(placement:.navigationBarTrailing){ Button { showLogOutChoice = true } label: { Text("Log Out ") } .confirmationDialog(Text:"Are You Sure You Want To Log Out?", isPresented: $showLogOutChoice , titleVisibility: .visible) { FBAuth.logout(completion: <#T##(Result<Bool, Error>) -> Void#>) } } } } } }
How to present views one at a time in SwiftUI
I am looking to make a quiz app using SwiftUI. I have now designed all of the views, but I am having trouble displaying the questions correctly. I have a home view which for now just has a button that should display a questionView (has only one question) in order and one at a time. I am using a ForEach to step through each question in a questions array and display a questionView with that specific question, but the problem is that the ForEach displays every question at the same time instead of waiting for the question to be submitted. How can communicate with the ForEach (or any other function) to tell it when to start and stop showing questionViews. struct Home: View { #State var presentQuestionView: Bool = false #StateObject var questionViewModel: QuestionViewModel = QuestionViewModel() #State var isCompleted = false var body: some View { VStack { Spacer() Text("Guess Players") .font(.title2) .bold() .onTapGesture { presentQuestionView.toggle() } Spacer() } .sheet(isPresented: $presentQuestionView, content: { ForEach(questionViewModel.questions.indices) { index in QuestionView(question: questionViewModel.questions[index], presentQuestionView: $presentQuestionView, progressWidth: questionViewModel.progress(currentIndex: index) ) .offset(x: questionViewModel.currentQuestion().isCompleted ? 1000 : 0) .rotationEffect(.init(degrees: questionViewModel.currentQuestion().isCompleted ? 1000 : 0)) } }) } } when the button is clicked, I want the ForEach statement to present the QuestionView one at a time. Here is my QuestionView struct QuestionView: View { #ObservedObject var question: Question #Binding var presentQuestionView: Bool var progressWidth: CGFloat #State var isSubmitted = false #State var buttonText = "Submit" var body: some View { VStack { ZStack(alignment: Alignment(horizontal: .leading, vertical: .center), content: { Capsule() .fill(Color.gray.opacity(0.7)) .frame(height: 6) Capsule() .fill(Color.green) .frame(width: progressWidth, height: 6) }) Text("Guess The Player!") .font(.system(size: 38)) .fontWeight(.heavy) .foregroundColor(.purple) .padding(.top) Text("Career Averages: ") .font(.title2) .fontWeight(.heavy) .foregroundColor(.black) .padding(.top, 8) Text("Points Per Game: \(question.answer.stats!.pointsPerGame)") .font(.system(size: 20)) .fontWeight(.heavy) .foregroundColor(.black) .padding(.top, 5) Text("Assists Per Game: \(question.answer.stats!.assistsPerGame)") .font(.system(size: 20)) .fontWeight(.heavy) .foregroundColor(.black) .padding(.top, 5) Text("Rebounds Per Game: \(question.answer.stats!.reboundsPerGame)") .font(.system(size: 20)) .fontWeight(.heavy) .foregroundColor(.black) .padding(.top, 5) Spacer(minLength: 0) LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 20), count: 2), spacing: 25, content: { Player_Cards(question: question, isSubmitted: $isSubmitted) }) .padding() Spacer(minLength: 0) Button(action: { if isSubmitted == false { //update the score if question.isCorrect() { } else { } //update the view to show the correct answer isSubmitted.toggle() buttonText = "Next Question" } else { //Go into the next question } }, label: { Text(buttonText) .fontWeight(.heavy) .foregroundColor(.white) .padding(.vertical) .frame(maxWidth: .infinity) .background(Color.blue) }) } .background(Color.black.opacity(0.05).ignoresSafeArea()) } } This is what currently happens when I run this project
Load image from sever inside image slider using swiftUI
I am trying to load image from server in a slide show with SwiftUi but image is not loading in the slide show. please see my code below import SwiftUI import Combine struct ImageCarouselView<Content: View>: View { private var numberOfImages: Int private var content: Content #State private var currentIndex: Int = 0 private let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect() init(numberOfImages: Int, #ViewBuilder content: () -> Content) { self.numberOfImages = numberOfImages self.content = content() } var body: some View { GeometryReader { geometry in // 1 ZStack(alignment: .bottom) { HStack(spacing: 0) { self.content } .frame(width: geometry.size.width, height: geometry.size.height, alignment: .leading) .offset(x: CGFloat(self.currentIndex) * -geometry.size.width, y: 0) .animation(.spring()) .onReceive(self.timer) { _ in self.currentIndex = (self.currentIndex + 1) % (self.numberOfImages == 0 ? 1 : self.numberOfImages) } // 2 HStack(spacing: 3) { // 3 ForEach(0..<self.numberOfImages, id: \.self) { index in // 4 Circle() .frame(width: index == self.currentIndex ? 10 : 8, height: index == self.currentIndex ? 10 : 8) .foregroundColor(index == self.currentIndex ? Color.blue : .white) .overlay(Circle().stroke(Color.gray, lineWidth: 1)) .padding(.bottom, 8) .animation(.spring()) } } } } } } and in my view struct MainView: View { #Binding var showMenu: Bool #State var index = 0 var body: some View { ScrollView { //MainView(with:"https://govcard.net/banners/banner1.jpg") GeometryReader { geometry in ImageCarouselView(numberOfImages: 3) { Image("image_carousel_1") .resizable() .scaledToFill() .frame(width: geometry.size.width, height: geometry.size.height) .clipped() Image("image_carousel_2") .resizable() .scaledToFill() .frame(width: geometry.size.width, height: geometry.size.height) .clipped() Image("image_carousel_3") .resizable() .scaledToFill() .frame(width: geometry.size.width, height: geometry.size.height) .clipped() } }.frame(height: 200, alignment: .center) } i want to use url instead of asset image.
As far as I've seen the easiest way would be to add the Kingfisher Swift package to your project.import KingfisherSwiftUI to your swift file and use the KFImage(myUrl). import KingfisherSwiftUI struct MainView: View { //... KFImage(URL(string: "https://example.com/image.png")!) } Here are the steps to add the Kingfisher to your project: Select File > Swift Packages > Add Package Dependency. Enter https://github.com/onevcat/Kingfisher.git in the "Choose Package Repository" dialog. In the next page, specify the version resolving rule as "Up to Next Major" with "5.14.0" as its earliest version.
SwiftUI: Problem with 3DEffect in a ScrollView
in my Projekt I have a ScrollView with some elements. When I am scrolling the elements should perform a 3DEffect. Everything works fine, but the edges of the Element (a VStack) are cut off. Does anybody know the problem? Here is my code of the ContentView(): var body: some View { NavigationView{ ZStack { VStack{ ScrollView(showsIndicators: false){ VStack(spacing: 30) { ForEach(0..<4){ item in GeometryReader { geometry in Elemente(minY: geometry.frame(in: .global).minY, title: self.möglicheZeiten[item < self.möglicheZeiten.count ? item : 0], einstellungsFeld: item ) } .frame(maxWidth: .infinity) .frame(width: 350, height: 200) } } } NavigationLink(destination: TrainingView()){ Text("Start") } .styleButton() } .navigationBarTitle("Workout", displayMode: .automatic) } } } And here is the code of the Element(): var body: some View{ var minY: CGFloat ... VStack { VStack(spacing: 20){ Text("\(title)") .bold() if einstellungsFeld != 3{ Zeiten(minuten: daten.berechneMinuten(minuten: Int(wert)), sekunden: daten.berechnenSekunden(sekunden: Int(wert))) }else{ Text("Durchgänge: \(Int(daten.durchgaenge))") } Slider(value: $daten.insgesamt) } .styleSettingsBox() .rotation3DEffect(Angle(degrees: Double(minY/10)), axis: (x: 10, y: 0, z: 0.0)) .scaleEffect(minY < 1.00 ? minY / 1000 + 1 : 1.00, anchor: .bottom) } } Thanks in advance and sorry for my English :)
I solved it by giving the Vstack in the ScrollView a .frame(maxHeight: .infinty).