SwiftUI 中的动画文本循环

Posted

技术标签:

【中文标题】SwiftUI 中的动画文本循环【英文标题】:Animating text cycle in SwiftUI 【发布时间】:2021-10-30 12:07:48 【问题描述】:

几天来,我一直在努力使我的应用程序具有某种“启动画面”,但无法完全按照我需要的方式实现它。

第一次打开应用程序时,我希望它通过不同的指令显示一个循环。例如,页面应该先说“欢迎”,然后在 2 秒后更改为“感谢您下载此应用”,然后在 2 秒后淡入我的主要内容视图。之后,我在 UI 按钮上方的某些位置出现了动画气泡,告诉用户如果单击它们会发生什么。

到目前为止,我的工作正常。为此,我有多个视图的 ZStack,经过延迟,旧视图的不透明度设置为 100%,因此出现了下面的视图。可以在下面找到主要代码,为简单起见删除了重复的功能。主屏幕视图是带有上述 UI 按钮的主视图。 setupscreen 视图包含给用户的文本说明。

代码如下:

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()
    

但是,在应用程序中,我有一个设置,用户可以单击以“重置”应用程序。当用户单击该按钮时,我将选项卡切换回上面驻留的选项卡,然后将所有 animateSplash 和 endSplash 变量重置为 false。这导致 setupscreen() 视图被显示(如预期的那样),但它永远不会再次淡出以在 ZStack 中显示其下方的视图。

我意识到我的实现方式是错误的,必须有一个更简单的方法。

任何帮助将不胜感激!

【问题讨论】:

【参考方案1】:

我无法完全重现您的情况,但根据我从您的代码中得出的信息,以下代码可能会有所帮助。

您正在使用带有不透明度的 ZStack。为什么不使用带有枚举的开关,该枚举包含所有视图并随着时间的推移在它们之间切换?

试试下面的示例代码:

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()
                
            
        
    

枚举包含要显示的当前视图。 .onAppear 管理变量 currentSplashView 中的状态变化。 VStack 中的切换最终在不同视图之间切换。一旦到达主页视图,计时器就会失效并停留在主页视图上。 (您可以在 Timer 中更改经过的时间)

重新加载整个视图后,计时器会再次启动,并且所有视图都会再次单独显示。当您“重置”应用程序时,您可以销毁并实例化此结构。

要为视图之间的过渡设置动画,请将“self.currentSplashView.next()”包装在 withAnimation 块中。


已添加

由于您想要一些其他结构并将飞溅与家庭分离,请为您的应用尝试以下结构:

@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()
                
            
        
    

将来,如果您有任何其他视图想要在没有 NavigationView 的情况下访问,只需将它们添加到枚举和主层次结构的开关中即可。

【讨论】:

谢谢,我让它工作了,但我不知道如何从另一个视图重置它。当您说要销毁和实例化时,您是什么意思?对于上下文,用户导航离开主视图,并且用户在其他视图中重置应用程序。主视图是 tab1,第二个视图是 tab2。 我不确定我是否完全理解了您正在实现的视图层次结构,但据我了解,您打开应用程序,显示 TestView(我创建的那个)。到达主屏幕视图后,用户可以在主视图中导航到另一个选项卡(设置)。在那里,用户可以重置应用程序。它是否正确?通过销毁和实例化,我的意思是当 TestView 留给 Tab2View 并且您再次将用户重定向到 TestView 时,整个过程和启动画面会再次显示。请参阅我的更新答案。 @Carl Bryers 如果这给了你预期的结果,你能给我一个更新吗? 嗨@Bjorn。抱歉,我不在,所以还没有机会实施您的更新。一旦我试一试,我会在下周通知你。非常感谢。 @CarlBryers And?

以上是关于SwiftUI 中的动画文本循环的主要内容,如果未能解决你的问题,请参考以下文章

动画 SwiftUI 文本编辑器文本

同一视图中的 SwiftUI 动画显示方式不同

调整大小时的 SwiftUI 文本剪辑

为 ForEach 循环元素添加动画 (SwiftUI)

SwiftUI 中的动画日期变化

SwiftUI Segmented Control 在视图刷新时选择段文本动画