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!
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#>)
}
}
}
}
}
}
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
I have a View that will display over another View. The view animation slides in from the right perfectly, but when I click on the Close button, the view disappears without the desired animation of sliding back to the right before disappearing.
I have tried using .opacity(self.isShowing ? 1 : 0), but then the View fades in and out, I need it to slide in and out. Other variations have not produced the desired results.
What am I doing wrong? Any guidance, even a duplicate solution (that I could not find) would be greatly appreciated.
struct NotificationView<parentView>: View where parentView: View {
#Binding var isShowing: Bool
let parentView: () -> parentView
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .center) {
self.parentView()
if(self.isShowing == true){
VStack {
Text("This is a test view\n")
Button(action: {
self.isShowing.toggle()
}) {
Text("Close")
}
}
.frame(width: geometry.size.width, height: geometry.size.height)
.background(Color(UIColor.systemBackground))
// .opacity(self.isShowing ? 1 : 0)
.transition(.move(edge: self.isShowing ? .trailing : .leading))
.animation(Animation.easeInOut(duration: 1.0))
}
}
}
}
}
Move conditional part into container and add animation to container, so it will animate content, like
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .center) {
self.parentView()
VStack { // << here !!
if(self.isShowing == true){
VStack {
Text("This is a test view\n")
Button(action: {
self.isShowing.toggle()
}) {
Text("Close")
}
}
.frame(width: geometry.size.width, height: geometry.size.height)
.background(Color(UIColor.systemBackground))
.transition(.move(edge: self.isShowing ? .trailing : .leading))
}
}.animation(Animation.easeInOut(duration: 1.0)) // << here !!
}
}
}
hi guys im learning swiftUI and i have some problem with my project.
i have one main card that will rotate 5 random card, plus the back of the card. and at the bottom 5 button that represent the 5 random card.
when i press any of the 5 buttons to rotate the card, i would like that the card rotate back automatically on the cardBack after 2 sec.
here is my code :
import SwiftUI
struct CardBack: View {
var body: some View {
Image("back_card")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 250)
}
}
struct ContentView: View {
#State var flipped = false
#State private var cardsFront = ["bigCard1", "bigCard2", "bigCard3", "bigCard4", "bigCard5" ]
#State private var cardBack = "back_card"
var body: some View {
VStack {
Spacer()
ZStack {
Image(flipped ? self.cardsFront.randomElement()! : self.cardBack)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 250)
.rotation3DEffect(Angle(degrees: flipped ? 180 : 0 ), axis: (x: 0, y: 1, z: 0))
}
Spacer()
HStack {
Button(action: {
withAnimation(.spring()) {
self.flipped.toggle()
}
}) {
Image("circle")
.renderingMode(.original)
}
Button(action: {
}) {
Image("plus")
.renderingMode(.original)
}
Button(action: {
}) {
Image("wave")
.renderingMode(.original)
}
Button(action: {
}) {
Image("square")
.renderingMode(.original)
}
Button(action: {
}) {
Image("star")
.renderingMode(.original)
}
}
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here is a demo on one button
Button(action: {
withAnimation(.spring()) {
self.flipped.toggle()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation(.spring()) {
self.flipped.toggle()
}
}
}) {
Image("circle")
.renderingMode(.original)
}