1

I've been struggling for days to make my app have a "splash screen" of sorts, but can't quite implement it in the way I need.

The first time the app is opened, I want it to display a cycle through different instructions. For example, the page should first say "welcome", then after 2 seconds, change to "thank you for downloading this app," then 2 seconds later fade to my main content view. After that, I have animated bubbles that appear in certain locations above UI buttons to tell the user what happens if you click them.

So far, I have that working fine. To do it, I have a ZStack of multiple views, and after a delay, the opacity of the old view is set to 100%, so the view below appears. The main code can be found below, with repetitions of the functions removed for simplicity. The homescreen view is the main view with the UI buttons mentioned above. The setupscreen views contain the text instructions to the user.

The code looks like this:

ZStack {

     homescreen()
     
     setupScreen6()
     
         .onAppear(perform: animateSplash6)
         .opacity(settings.endSplash6 ? 0 : 1)
         .opacity(settings.animate6 ? 1 : 0)
         
     
     setupScreen5()
         .onAppear(perform: animateSplash5)
         .opacity(settings.endSplash5 ? 0 : 1)
         .opacity(settings.animate5 ? 1 : 0)
     
     setupScreen4()
         .onAppear(perform: animateSplash4)
         .opacity(settings.endSplash4 ? 0 : 1)
     
     setupScreen3()
         .onAppear(perform: animateSplash3)
         .opacity(settings.endSplash3 ? 0 : 1)
     
     setupScreen2()
         .onAppear(perform: animateSplash2)
         .opacity(settings.endSplash2 ? 0 : 1)
     
     
     setupScreen()
         .onAppear(perform: animateSplash)
         //.onAppear(perform: endanimateSplash)
         .opacity(settings.endSplash ? 0 : 1)
     
        
     
 

 
 }.environmentObject(settings)

}

func animateSplash() {

DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 
    
    withAnimation(Animation.easeOut(duration: 1)) {
        settings.animate.toggle()
    }
    
    withAnimation(Animation.easeOut(duration: 1)) {
        settings.endSplash.toggle()
    }

}

}

However, in the app, I have a setting that the user can click to "reset" the app. When the user clicks that button I switch tabs back to the tab in which the above resides, then reset all of the animateSplash and endSplash variables back to false. This results in the setupscreen() view being displayed (as expected), but it never fades out again to reveal the views below it in the ZStack.

I'm realising my way of implementing this is wrong, and there has to be a simpler way.

any help would be appreciated!

1 Answer 1

2

I cannot fully reproduce your situation, but from what I can derive from your code, the following code could be helpful.

You are using a ZStack with opacities. Why not using a switch with an enum that contains all the views and switches between them as some time goes by?

Try the following sample code:

struct TestView: View {
    
    enum ViewState: CaseIterable {
        case splash1
        case splash2
        case splash3
        case home
        
        mutating func next() {
            let allCases = type(of: self).allCases
            self = allCases[(allCases.firstIndex(of: self)! + 1) % allCases.count]
        }
    }
    
    @State var currentSplashView: ViewState = .splash1
    
    var body: some View {
        
        VStack {
            switch self.currentSplashView {
            case .splash1:
                Text("1")
                // setupScreen1()
            case .splash2:
                Text("2")
                // setupScreen2()
            case .splash3:
                Text("3")
                // setupScreen3()
            case .home:
                Text("home")
                // homescreen()
            }
        }
        .onAppear {
            Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { timer in
                self.currentSplashView.next()
                if self.currentSplashView == .home {
                    timer.invalidate()
                }
            }
        }
    }
}

The enum holds the current View that is to de displayed. The .onAppear{} manages the state changes in the variable currentSplashView. The switch in the VStack finally switches between the different views. Once the Home view has been reached, the timer is invalidated and stays on the homeview. (You can change the amount of time to go by in the Timer)

Once this whole view is reloaded, the timer starts again and the views are all individually displayed again. When you 'reset' the app, you could destroy and instantiate this structure.

To animate the transitions between the views, wrap 'self.currentSplashView.next()' in a withAnimation{} block.


Added

Since you want some other structure and decouple the splash from the home, try the following structure for your app:

@main
struct YourAppName: App {

    var body: some Scene {
        WindowGroup {
            ViewRouter(currentView: .splash) // <---- you can determine if you want to show splash or go to home immediately
        }
    }
}

enum Views {
    case home
    case settings
    case splash
}

struct ViewRouter: View {
    
    @State var currentView: Views

    var body: some View {
        
        VStack {
            switch self.currentView {
            case .home:
                HomeView(currentView: self.$currentView)
            case .settings:
                Settings(currentView: self.$currentView) // <---- in this view reset the app
            case .splash:
                SplashView(currentView: self.$currentView) // <--- this is the view I created earlier. Remove the .home case from that view, we move home 1 level up in the hierarchy (watch the changes closely)
            }
        }
    }
}

struct HomeView: View {

    @Binding var currentView: Views

    var body: some View {
        
        VStack {
            
           Text("This is your home").font(.largeTitle)
            
            Button(action: {
                withAnimation {
                    self.currentView = .settings
                }
            }){
                Text("Go to settings")
            }

        }
    }
}

struct Settings: View {

    @Binding var currentView: Views

    var body: some View {
        
        VStack {
            
           Text("This is your settings").font(.largeTitle)
            
            Button(action: {
                withAnimation {
                    self.currentView = .splash
                }
            }){
                Text("Reset app") // go to start from app and star with splash
            }

        }
    }
}

struct SplashView: View {
    
    enum ViewState: CaseIterable {
        case splash1
        case splash2
        case splash3
        
        mutating func next() {
            let allCases = type(of: self).allCases
            self = allCases[(allCases.firstIndex(of: self)! + 1) % allCases.count]
        }
    }
    
    @Binding var currentView: Views
    @State var currentSplashView: ViewState = .splash1
    
    var body: some View {
        
        VStack {
            switch self.currentSplashView {
            case .splash1:
                Text("Splash 1").font(.largeTitle)
                // setupScreen1()
            case .splash2:
                Text("Splash 2").font(.largeTitle)
                // setupScreen2()
            case .splash3:
                Text("Splash 3").font(.largeTitle)
                // setupScreen3()
            }
        }
        .onAppear {
            Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { timer in
                if self.currentSplashView == .splash3 {
                    timer.invalidate()
                    withAnimation {
                        self.currentView = .home // <---- when the last splash is reached, set the hierarchy to home
                    }
                }
                
                withAnimation {
                    self.currentSplashView.next()
                }
            }
        }
    }
}

In the future, if you have any other views that you want to reach without a NavigationView, just add these to the enum and the switch of the main hierarchy.

Sign up to request clarification or add additional context in comments.

7 Comments

Thanks, I got that to work, but I can’t figure out how to reset it from another view. What do you mean when you say to destroy and instantiate? For context, the user navigates away from the homeview and it’s within that other view that the user resets the app. The homeview is tab1 and the second view is in tab2.
I am unsure if I have completely understood the view hierarchy you are implementing, but from what I understood you open the app, show the TestView (the one I created). Once the homescreen view has been reached, the user can navigate within the homeview to another tab (settings). In there, the user could reset the app. Is this correct? With destroying and instantiating I mean that when the TestView is left for Tab2View and you redirect the user to TestView again, the entire process and splash is shown again. See my updated answer.
@Carl Bryers Could you give me an update if this gave you the expected result?
Hi @Bjorn. Sorry, I’ve been away so haven’t had a chance to implement your update just yet. I’ll let you know next week once I’ve given it a go. Many thanks.
@CarlBryers And?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.