在 SwiftUI 中以编程方式移动 NavigationView 的最佳实践是啥

Posted

技术标签:

【中文标题】在 SwiftUI 中以编程方式移动 NavigationView 的最佳实践是啥【英文标题】:What's best practice for programmatic movement a NavigationView in SwiftUI在 SwiftUI 中以编程方式移动 NavigationView 的最佳实践是什么 【发布时间】:2021-01-29 00:30:59 【问题描述】:

我正在开发一个需要在用户上次使用的视图上打开的应用,即使该应用已被用户或 ios 完全杀死。

因此,我保留了 UserDefaults 中使用的最后一个视图,并自动将用户移动到堆栈中的每个视图,直到他们到达目的地。

每个视图上的代码如下:

@Binding var redirectionID: Int

VStack() 
  List 
    NavigationLink(destination: testView(data: data, moc: moc), tag: data.id, selection: 
         $redirectionId) 
                        
                           DataRow(data: data)
                        
                         
        
.onAppear() 
  redirectionID = userData.lastActiveView

有没有更好/标准的方法来实现这一点?这在 iOS 14.* 上运行合理。* 但在 iOS 13.* 上运行不佳。* 在 iOS 13.* 上,重定向通常不会到达其目标页面,并且似乎没有创建堆栈中的先前视图。按下等会导致崩溃。

任何帮助/建议将不胜感激。

【问题讨论】:

我个人会直接显示所需的页面。转到父页面的代码应该是相当静态的。如果有“历史”的概念(意味着用户可以通过多条路径到达页面),我也会将历史存储在 usedDefault 中。 如果您直接进入所需的页面,您将如何处理用户从堆栈向下移动?例如,如果它在第三页打开,用户将如何导航回第一页? 【参考方案1】:

这听起来像是 if SceneStorage 的完美使用

“You use SceneStorage when you need automatic state restoration of the value. SceneStorage works very similar to State, except its initial value is restored by the system if it was previously saved, and the value is· shared with other SceneStorage variables in the same scene.”

@SceneStorage("ContentView.selectedProduct") private var selectedProduct: String?

@SceneStorage("DetailView.selectedTab") private var selectedTab = Tabs.detail

它仅在 iOS 14+ 中可用,但必须执行一些手动操作。也许在 CoreData 中有一些东西。每个重要状态变量都有变量的对象。它可以像 ObservedObject ViewModel 一样持久工作。

还有。你可以试试……

“NSUserActivity 对象捕获应用程序在当前时刻的状态。例如,包含有关应用程序当前正在显示的数据的信息。系统保存提供的对象并在下次启动时将其返回给应用程序。当用户关闭应用或应用进入后台时,示例会创建一个新的 NSUserActivity 对象。”

这里有一些示例代码总结了如何将它们组合在一起。这不是一个最小的可重现示例,因为它是来自 Apple 的名为“Restoring Your App's State with SwiftUI”的更大项目的一部分。但它给出了如何实现它的一个很好的画面。

struct ContentView: View 
    // The data model for storing all the products.
    @EnvironmentObject var productsModel: ProductsModel
    
    // Used for detecting when this scene is backgrounded and isn't currently visible.
    @Environment(\.scenePhase) private var scenePhase

    // The currently selected product, if any.
    @SceneStorage("ContentView.selectedProduct") private var selectedProduct: String?
    
    let columns = Array(repeating: GridItem(.adaptive(minimum: 94, maximum: 120)), count: 3)
    
    var body: some View 
        NavigationView 
            ScrollView 
                LazyVGrid(columns: columns) 
                    ForEach(productsModel.products)  product in
                        NavigationLink(destination: DetailView(product: product, selectedProductID: $selectedProduct),
                                       tag: product.id.uuidString,
                                       selection: $selectedProduct) 
                            StackItemView(itemName: product.name, imageName: product.imageName)
                        
                        .padding(8)
                        .buttonStyle(PlainButtonStyle())
                        
                        .onDrag 
                            /** Register the product user activity as part of the drag provider which
                                will  create a new scene when dropped to the left or right of the iPad screen.
                            */
                            let userActivity = NSUserActivity(activityType: DetailView.productUserActivityType)
                            
                            let localizedString = NSLocalizedString("DroppedProductTitle", comment: "Activity title with product name")
                            userActivity.title = String(format: localizedString, product.name)
                            
                            userActivity.targetContentIdentifier = product.id.uuidString
                            try? userActivity.setTypedPayload(product)
                            
                            return NSItemProvider(object: userActivity)
                        
                    
                
                .padding()
            
            .navigationTitle("ProductsTitle")
        
        .navigationViewStyle(StackNavigationViewStyle())
        
        .onContinueUserActivity(DetailView.productUserActivityType)  userActivity in
            if let product = try? userActivity.typedPayload(Product.self) 
                selectedProduct = product.id.uuidString
            
        
        .onChange(of: scenePhase)  newScenePhase in
            if newScenePhase == .background 
                // Make sure to save any unsaved changes to the products model.
                productsModel.save()
            
        
    

【讨论】:

这看起来很有希望。我将查看 SceneStorage 并发布任何进展。谢谢! 我现在正在查看多个窗口,WWDC19 - Introducing Multiple Windows on iPad 视频中有很多关于场景恢复的信息以及一些示例核心 传奇,明天早上我会玩这个,让你知道它是怎么回事。

以上是关于在 SwiftUI 中以编程方式移动 NavigationView 的最佳实践是啥的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SwiftUI 中以编程方式计算 MKMapCamera centerCoordinateDistance

如何在 iOS 应用程序中以编程方式检测 SwiftUI 的使用情况

Push、Segue、Summon、导航以编程方式查看 SwiftUI

在方法中以编程方式移动控件警告“未找到方法”和“控件可能无法响应”

如何在UITextView中以编程方式移动光标?

在 SwiftUI 中以简单的方式为 TabView tabItem 添加徽章值