Reputation: 8590
I initially had this question here. The solution proposed by @arsenius was working for this toy example. However, my application is more complex and it took me forever to find out where the animation breaks. In the example I used two animated HStack
. But if I replace these HStack
with two different (!) custom views, the animation is broken again.
Here is the code:
class State:ObservableObject{
@Published var showSolution = false
}
struct ContentView: View {
@EnvironmentObject var state:State
var body:some View {
VStack {
if state.showSolution{
CustomToggleOne()
.background(Color.red)
.id("one")
.animation(Animation.default)
.transition(.slide)
} else {
CustomToggleTwo()
.background(Color.yellow)
.id("two")
.animation(Animation.default.delay(2))
.transition(.slide)
}
}
}
}
struct CustomToggleOne: View{
@EnvironmentObject var state:State
var body:some View{
HStack{
Spacer()
Button(action:{
withAnimation {
self.state.showSolution.toggle()
}
}){
Text("Next")
}.padding()
Spacer()
}
}
}
struct CustomToggleTwo: View{
@EnvironmentObject var state:State
var body:some View{
HStack{
Spacer()
Button(action:{
withAnimation {
self.state.showSolution.toggle()
}
}){
Text("Next")
}.padding()
Spacer()
}
}
}
I added an instance of State
to the ContentView in SceneDelegate.swift
as an EnvironmentObject
as follows:
let contentView = ContentView().environment(\.managedObjectContext, context).environmentObject(State())
The expected animation can be seen when we use CustomToggleOne()
twice in the ContentView
instead of CustomToggleTwo()
.
Upvotes: 3
Views: 2170
Reputation: 258107
Here is the approach that is correct, some explanations below and in comments. Tested with Xcode 11.2 / iOS 13.2
Reminder: don't test transitions in Preview - only on Simulator or Device
// !! Don't name your types as API,
// State is SwiftUI type, might got unpredictable weird issues
class SolutionState:ObservableObject{
@Published var showSolution = false
}
struct TestBrokenAnimation: View {
@EnvironmentObject var state:SolutionState
var body:some View {
VStack {
if state.showSolution{
CustomToggleOne()
.background(Color.red)
.id("one")
.transition(.slide) // implicit animations confuse transition, don't use it
} else {
CustomToggleTwo()
.background(Color.yellow)
.id("two")
.transition(.slide)
}
}
}
}
public func withAnimation<Result>(_ animation: Animation? = .default, _ body: () throws -> Result) rethrows -> Result
as it seen withAnimation
is not state, which just activate animations defined via modifier, it explicitly applies specified animation, own, so having more in modifiers will definitely result in some conflicts.
So using only explicit animations with transitions.
struct CustomToggleOne: View{
@EnvironmentObject var state:SolutionState
var body:some View{
HStack{
Spacer()
Button(action:{
withAnimation(Animation.default.delay(2)) {
self.state.showSolution.toggle()
}
}){
Text("Next")
}.padding()
Spacer()
}
}
}
struct CustomToggleTwo: View{
@EnvironmentObject var state:SolutionState
var body:some View{
HStack{
Spacer()
Button(action:{
withAnimation() {
self.state.showSolution.toggle()
}
}){
Text("Next")
}.padding()
Spacer()
}
}
}
Upvotes: 4