【问题标题】:SwiftUI NavigationLink behaviour is different when selection is set directly, rather than from viewModel直接设置选择而不是从 viewModel 设置选择时,SwiftUI NavigationLink 行为不同
【发布时间】:2021-03-06 15:07:42
【问题描述】:

我一直在使用 SwiftUI,但遇到了意外的行为。

我有视图 A、视图 B 和视图 C。视图 C 有 EnviromentObject,它从视图 A 更改 AppState

视图 B 具有带有选择的 ViewModel

如果我从 ViewModel 调用函数来更改选择,那么 视图 C 显示几秒钟,然后自动弹回视图 B

如果我直接从视图 B(而不是视图模型)更改选择,一切都会按预期工作。 另外,如果我注释掉Dissapear,它也可以工作。但是,当屏幕消失时,我需要更改 environmentObject 这是视图 B 和视图模型

import SwiftUI

class AppState: ObservableObject {
    @Published
    var shouldHideUserInfo = false
}


struct ContentView: View {
    
    @EnvironmentObject
    var appState: AppState
    
    @State
    var selection: Int? = nil
    
    var body: some View {
        NavigationView {
            VStack {
                if !appState.shouldHideUserInfo {
                    Text("USER INFO")
                }
                
                NavigationLink(
                    destination: ViewA(),
                    tag: 1,
                    selection: $selection,
                    label: { EmptyView()})
                
                Button("MOVE TO VIEW A") {
                    selection = 1
                }
            }
        }
    }
}


class ViewAModel: ObservableObject {
    @Published
    var selection: Int? = nil
    
    func navigate() {
        selection = 2 //<- this doesnt
    }
}

struct ViewA: View {
    
    @ObservedObject
    var viewModel: ViewAModel
    
    init() {
        viewModel = ViewAModel()
    }

    @State
    var selection: Int? = nil //<- this works
    
    var body: some View {
        VStack
        {
            Text("VIEW A")
            
            NavigationLink(
                destination: ViewB(),
                tag: 2,
                selection: $viewModel.selection,
                label: { EmptyView()})
            
            Button("MOVE TO VIEW B") {
                //selection = 2 <-- this works
                viewModel.navigate() //<- this doesnt
            }
           
        }
    }
}

struct ViewB: View {
    
    @EnvironmentObject
    var appState: AppState

    @State
    var selection: Int? = nil
    
    var body: some View {
        VStack
        {
            Text("VIEW B")
           
        }
        .onAppear {
            appState.shouldHideUserInfo = true
        }
    }
}

工厂模式没有解决问题:

    static func makeViewA(param: Int?) -> some View {
        let viewModel = ViewAModel(param: param)
        return ViewA(viewModel: viewModel)
    }
}

【问题讨论】:

  • 与 Xcode 12.1 / iOS 14.1 相同。您使用哪种环境?
  • @Asperi 我的环境是一样的。有一次,我创建了一个最小的 git repo,这个问题是可重现的。 github.com/martynasNarijauskas/swiftui
  • + 我更新了当前代码,所以只需复制它并从场景委托设置 env 对象就足够了

标签: swiftui swiftui-navigationlink swiftui-navigationview


【解决方案1】:

我明白了……这与帖子中的有点不同。问题是因为视图模型被重新创建(这是NavigationView 的长期观察行为),因此绑定丢失。

快速解决方法是

struct ViewA: View {
    
    @StateObject
    var viewModel: ViewAModel = ViewAModel()
    
    init() {
//        viewModel = ViewAModel()
    }

    // ... other code
}

替代方法是将视图模型的所有权保留在 ViewA 之外。

更新:兼容 SwiftUI 1.0 - 这是一个适用于任何地方的变体。问题的原因在AppState。 ViewB中的代码更新appState

.onAppear {
    appState.shouldHideUserInfo = true
}

这会导致ContentView 主体的重建,它重新创建ViewA,它重新创建NavigationLink,它删除了先前的链接并且ViewB 被关闭。

为了防止这种情况,我们需要避免重建ViewA。这可以通过使 ViewA is-a Equatable 来完成,因此 SwiftUI 检查是否需要重新创建 ViewA,我们将回答 NO。

事情是这样的:

NavigationLink(
    destination: ViewA().equatable(),    // << here !!
    tag: 1,
    selection: $selection,
    label: { EmptyView()})

和

struct ViewA: View, Equatable {
    static func == (lhs: ViewA, rhs: ViewA) -> Bool {
        true
    }
    
    // .. other code

【讨论】:

  • 能否请您更具体地说明如何将 ViewModel 的所有权保留在 ViewA 之外?我尝试使用工厂模式创建静态函数,以使用已注入的 ViewModel 创建 ViewA,但它并没有解决问题。我用代码更新我的问题
  • 我还是要支持ios 13 :(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-29
  • 1970-01-01
  • 2023-03-24
  • 2020-04-19
  • 1970-01-01
相关资源
最近更新 更多