Reputation: 1172
I have several cards with translations of word. ChooseTrainingView
is my view which get word as model and success handler. When I click on translation it changes training.currentWord
so I expect animation between two views. First view should slide to left and then second view shows from right with slide. But my code below has strange behaviour:
Group {
ForEach(training.words) { w in
if w == training.currentWord {
ChooseTrainingView(word: training.currentWord) { success in
withAnimation(.easeIn(duration: 0.5)) {
_ = training.goToNext(success: success)
}
}
}
}
}
.transition(.slide)
Animation has only a part of views to be animated and only in view appearing. There is no disappearing view animation.
I tried to not use ForEach
, but I cannot understand how to animate full view when I change currentWord
- the view just change word at top and safe its state instead of creating new view with new values from currentWord
Also I cannot find examples how to animate sliding right-to-left views by replacing the model for view
Upvotes: 0
Views: 828
Reputation: 1172
According to @J W answer I found the solution. We should to add .id
modifier to our view. Besides we don't need ForEach
anymore and it means that we don't need to use array of words
. Just one word
(which conforms to Identifiable
) and when we change this word
our view will be fully recreated (reset its state). There is a code with the solution:
Group {
ChooseTrainingView(word: training.currentWord) { success in
withAnimation(.easeIn(duration: 0.5)) {
_ = training.goToNext(success: success)
}
}
.id(training.currentWord.id)
}
.transition(.slide)
In addition, I found very good article which explains how .id
modifier works:
https://swiftui-lab.com/swiftui-id/
Upvotes: 0
Reputation: 924
If you just want to change the animation
, it's very easy ୧(๑•̀⌄•́๑)૭
change the .transition(.slide)
to .transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing)))
If there is any other problem, please refer to the following content. If it really doesn’t work, please feel free to ask me again.
Example Code:
import SwiftUI
struct ChooseTrainingView: View {
var word: String
var successHandler: (Bool) -> Void
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 25.0)
.shadow(radius: 10)
.frame(width: 300, height: 400)
VStack {
HStack {
Text(word)
.font(.title)
.foregroundColor(.white)
Image(systemName: "speaker.wave.2")
.foregroundColor(.white)
}
Divider().background(Color.white)
VStack(alignment: .leading, spacing: 10) {
ForEach(0..<5) { index in
Text("Option \(index)")
.padding(.vertical, 8)
.padding(.horizontal)
.foregroundColor(.white)
.cornerRadius(10)
}
}
.padding()
}
.padding()
}
.onTapGesture {
self.successHandler(true)
print("successHandler")
}
}
}
struct ContentView: View {
@ObservedObject var training = TrainingModel(words: (0...5).map{Word(translation: String($0))})
var body: some View {
ZStack {
ForEach(training.words, id: \.self) { word in
if word == training.words[training.currentWordIndex] {
ChooseTrainingView(word: word.translation, successHandler: { success in
withAnimation {
_ = training.goToNext(success: success)
}
})
.id(word.id)
.transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing)))
}
}
}
}
}
struct Word: Hashable, Identifiable {
let id = UUID()
var translation: String
}
class TrainingModel: ObservableObject {
@Published var words: [Word]
@Published var currentWordIndex: Int
init(words: [Word]) {
self.words = words
self.currentWordIndex = 0
}
func goToNext(success: Bool) -> Bool {
if currentWordIndex < words.count - 1 {
currentWordIndex += 1
return true
} else {
return false
}
}
}
#Preview {
ContentView()
}
Upvotes: 2