Home > Mobile >  SwiftUI: Replace view of array with crossDissolve style animation
SwiftUI: Replace view of array with crossDissolve style animation

Time:12-14

I have a view with dynamic content. I want to add an animation (fadeOut/fadeIn) to switch to the next view

For this purpose I tried:

.transition(.opacity)
.animation(.easeInOut, value: tabSelection)

But doesn't work, I get a slide animation from top to bottom

My current code:

struct LongOnboardingContainerView: View {

    @State var tabSelection = 0

    var body: some View {
            ZStack {
                Step.allCases[tabSelection].view {
                    tabSelection  = 1
                }
                .transition(.opacity)
                .animation(.easeInOut, value: tabSelection)
        }
        .ignoresSafeArea(.all)
    }

    enum Step: Int, CaseIterable {
        case parentsGoal, nickname, age, mathSkills, languageSkill

        func view(pushNext: @escaping () -> ()) -> AnyView {
            switch self {
                case  parentsGoal mathSkills, languageSkill:
                    return AnyView(MultiAnswerView(viewModel: .init(step: self), pushNext: pushNext))
                case .nickname:
                    return AnyView(NickNameController.swiftUIRepresentation { vc in
                        vc.nextHandler = pushNext
                    })
                default:
                    return AnyView(Text("Test").onTapGesture {
                        pushNext()
                    })
            }
        }
    }
}

CodePudding user response:

I did an abstraction of your code and it shows that the transition does work with same underlying view types (Text), but does not with different ones (as for nickname and default).

So I guess the animation doesn't like to work together with AnyView type erasure.

let colors: [Color] = [.red, .yellow, .blue, .green, .teal, .gray]

struct ContentView: View {

    @State var tabSelection = 0

    var body: some View {
            ZStack {
                Step.allCases[tabSelection].view {
                    tabSelection =  (tabSelection   1) % Step.allCases.count
                }
                .transition(.opacity)
                .animation(.easeInOut(duration: 1), value: tabSelection)
        }
        .ignoresSafeArea(.all)
    }


    enum Step: String, CaseIterable {
        case parentsGoal, nickname, age, mathSkills, languageSkill

        
        func view(pushNext: @escaping () -> ()) -> AnyView {
            switch self {
            case  .parentsGoal, .mathSkills, .languageSkill:
                return AnyView(
                    Text("Multi Answer View \(self.rawValue)").font(.largeTitle)
                        .onTapGesture {  pushNext() }
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                        .background(colors.randomElement()!)
                )
            case .nickname:
                return AnyView(
                    VStack {
                        Image(systemName: "person")
                        Text("Nickname View").font(.largeTitle)
                    }
                        .onTapGesture {  pushNext() }
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                        .background(.orange)
                )
            default:
                return AnyView(Text("Test").onTapGesture {
                    pushNext()
                })
            }
        }
    }
}

CodePudding user response:

I found a workaround that simule crossDissolve animation:

    @State var tabSelection = 0
    @State var isVisible = true

    var body: some View {
        ZStack {
             if isVisible {
                stepView()
            } else {
                stepView()
            }    
        }
        .ignoresSafeArea(.all)
    }
    
func stepView() -> some View {
    Step.allCases[tabSelection].view {
        tabSelection  = 1
        isVisible.toggle()
    }
    .transition(AnyTransition.opacity.animation(.easeInOut(duration: 0.35)))
}
  • Related