如何将动画添加到由 AnyView 制作的自定义导航项之间的过渡?

Posted

技术标签:

【中文标题】如何将动画添加到由 AnyView 制作的自定义导航项之间的过渡?【英文标题】:How do I add Animations to Transitons between custom NavigationItems made from AnyView? 【发布时间】:2019-11-18 06:00:33 【问题描述】:

如this article 中所述,有没有一种方法可以将动画添加到由AnyView 制作的自定义NavigationItems 之间的过渡?

我喜欢这个NavigationStack 系统的一切,除了不能添加任何动画过渡。

我知道问题与使用AnyView 擦除some Views 相关,如this answer 中所述,并且Group 似乎是动画自定义视图导航的更好选择。

比起使用 AnyView 和类型擦除,我更喜欢将条件逻辑封装在组视图中。那么你返回的类型是Group,它会正确地动画。

我的想法已经用完了,我需要一些帮助。

我将为上下文添加我的代码。

场景代理:

class SceneDelegate: UIResponder, UIWindowSceneDelegate 
...
    let contentView = ContentView().environmentObject(UserData())
...

内容视图:

struct ContentView: View 
    @EnvironmentObject var userData: UserData   
    var body: some View 
        NavigationHost()
            .environmentObject(NavigationStack(
                NavigationItem(view: AnyView(
                    (userData.isSignedIn ? AnyView(HomeView()) : AnyView(RegisterView1(profile: Profile.default)) )
                    .transition(.move(edge: .trailing))
                    .animation(Animation.linear(duration: 1))))))
                    //this animation works for some reason, but only this one.
            .environmentObject(UserData())
    

导航主机:

struct NavigationHost: View
    @EnvironmentObject var navigation: NavigationStack
    @EnvironmentObject var userData: UserData

    var body: some View 
            ZStack 
                    self.navigation.currentView.view
                        .environmentObject(self.userData)
                        .environmentObject(self.navigation)
                    ...
                
            
    

导航栈:

final class NavigationStack: ObservableObject 
    @Published var viewStack: [NavigationItem] = []
    @Published var currentView: NavigationItem

    init(_ currentView: NavigationItem )
        print("Navigation Stack Initialized")
        self.currentView = currentView
    

    func back() 
        if viewStack.count == 0 
            return
        
        let last = viewStack.count - 1
        currentView = viewStack[last]
        viewStack.remove(at: last)
    

    navigation.advance(NavigationItem(AnyView))
    func advance(_ view: NavigationItem) 
        viewStack.append( currentView )
        currentView = view
    

    func home() 
       currentView = NavigationItem( view: AnyView(HomeView()) )
       viewStack.removeAll()
    



struct NavigationItem
    var view: AnyView


【问题讨论】:

据我从文章中得出的结论,在所提供的方法中几乎不可能过渡/动画,因为只要根视图中有一些视图层次结构更改,.transition 和 .animation 修饰符就会生效,即.一个视图被另一个视图替换,但在所描述的方法中 ContentView 始终只有一个 NavigationHost 视图。它是更新的,是的,但它是唯一的,没有替代过渡。 哦,我从没想过问题出在场景委托中。感谢您为我详细说明问题。 @Asperi 有没有办法避免这个问题?比如1:每次修改‘NavigationStack’时故意重新加载rootView,并传递一个‘.environmentObject(navigationStack)’给新的rootView,或者2:创建自定义动画?编辑:对不起,如果我似乎对 swiftUI 的工作方式感到困惑。 【参考方案1】:

这里是该文章中更新的实体,以采用屏幕之间的简单动画转换。所有这些都是沙哑的,仅用于演示,但希望它可以以某种方式有所帮助。

注意:转场在 Preview 中不起作用(至少在我的 Xcode 11.2/ios 13.2 中),因此在模拟器或真实设备中进行了测试。

它的外观如下:

代码如下:

final class NavigationStack: ObservableObject 
    enum Direction 
        case root
        case forward
        case backward
    

    @Published var viewStack: [NavigationItem] = []
    @Published var currentView: NavigationItem
    @Published var navigate: Direction = .root

    init(_ currentView: NavigationItem )
        self.currentView = currentView
    
    func unwind()
        if viewStack.count == 0
            return
        
        let last = viewStack.count - 1
        currentView = viewStack[last]
        withAnimation 
            self.navigate = .backward
        
        viewStack.remove(at: last)
    
    func advance(_ view:NavigationItem)
        viewStack.append( currentView)
        currentView = view
        withAnimation 
            self.navigate = .forward
        
    
    func home( )
        currentView = NavigationItem(view: AnyView(HomeView()))
        viewStack.removeAll()
        withAnimation 
            self.navigate = .root
        
    


struct NavigationHost: View
    @EnvironmentObject var navigation: NavigationStack

    var body: some View 
        ZStack(alignment: .topLeading) 
            if navigation.navigate == .root 
                self.navigation.currentView.view
            
            else if navigation.navigate == .backward 
                self.navigation.currentView.view
                    .transition(.move(edge: .leading))
            
            if navigation.navigate == .forward 
                self.navigation.currentView.view
                    .transition(.move(edge: .trailing))
            
        

    

【讨论】:

如此有趣的是,动画发生在最后一次渲染 body 和新渲染 view 之间,对吧?就像在最后一次调用 body 时没有呈现两个版本的视图(只显示最后一个视图),对吗? 吹毛求疵的小评论,也许值得使用 push/pop 函数名来模仿 UINavigationController 的 API?美好的旧时光? 另外,NavigationItem 的代码是什么?它有什么用? ...hm,这段代码实际上不起作用。从那以后有什么变化吗?使用此代码,您看到的是第 1 页、第 2 页、第 2 页的动画幻灯片? 这是一个糟糕的建议。这基本上破坏了 UINavigationController 内置的任何可访问性支持,以及提供的所有内置工具。就像网络“应用程序”这样做很糟糕一样,SwiftUI 也同样糟糕。

以上是关于如何将动画添加到由 AnyView 制作的自定义导航项之间的过渡?的主要内容,如果未能解决你的问题,请参考以下文章

React-Native:将焦点设置到由数组构建的自定义组件

如何将自定义 AxisItem 添加到现有的 PlotWidget?

如何制作可以添加到工具箱的自定义控件DLL?

DragonBones龙骨骨骼中的自定义事件(另有声音动画事件)

您如何在 WPF 中动态(通过代码)添加在 XAML 中制作的自定义控件?

拆分不同颜色的自定义 UIView