0

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()
                    })
            }
        }
    }
}
Mickael Belhassen
  • 2,970
  • 1
  • 25
  • 46

2 Answers2

0

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()
                })
            }
        }
    }
}
ChrisR
  • 9,523
  • 1
  • 8
  • 26
  • Can you explain this `tabSelection = (tabSelection + 1) % Step.allCases.count` Yes, I noticed that it works if the next view is of the same type. Is there any workaround for this? Even if we integrate UIKit, do you have any ideas? – Mickael Belhassen Dec 14 '22 at 06:27
  • the line just makes sure that `tabSelection` stays in its range of 0...4 by modulo dividing with `Step.allCases.count`(=5). So you can tap on the last view and get back to the first. It was just for the test set-up to not crash when tapping on the last view. – ChrisR Dec 14 '22 at 07:33
  • for anyview and animation this should help: https://stackoverflow.com/questions/58160011/swiftui-add-subview-dynamically-but-the-animation-doesnt-work – ChrisR Dec 14 '22 at 07:35
0

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)))
}
Mickael Belhassen
  • 2,970
  • 1
  • 25
  • 46