【问题标题】:Push View programmatically in callback, SwiftUI在回调中以编程方式推送视图,SwiftUI
【发布时间】:2019-12-10 10:16:34
【问题描述】:

在我看来,Apple 是在鼓励我们放弃在 SwiftUI 中使用UIViewController,但不使用视图控制器,我感觉有点力不从心。我想要的是能够实现某种ViewModel,它将向View发出事件。

视图模型

public protocol LoginViewModel: ViewModel {
  var onError: PassthroughSubject<Error, Never> { get }
  var onSuccessLogin: PassthroughSubject<Void, Never> { get }
}

查看

public struct LoginView: View {
  fileprivate let viewModel: LoginViewModel
  
  public init(viewModel: LoginViewModel) {
    self.viewModel = viewModel
  }
  
  public var body: some View {
    NavigationView {
      MasterView()
        .onReceive(self.viewModel.onError, perform: self.handleError)
        .onReceive(self.viewModel.onSuccessLogin, perform: self.handleSuccessfullLogin)
    }
  }

  func handleSuccessfullLogin() {
    //push next screen
  }
  
  func handleError(_ error: Error) {
    //show alert
  }
}

使用 SwiftUI,如果登录成功,我不知道如何推送另一个控制器

另外,如果您能提供任何关于如何以更好的方式实现我想要的东西的建议,我将不胜感激。谢谢。

【问题讨论】:

  • 你是对的,如果你有一个SwiftUI 项目,恕我直言,如果你需要使用代表之类的东西或(还) 在原生 SwiftUI 中可用。让我们从第一格开始;您的模型需要驱动视图,而不是相反。它可能应该是一个符合ObservableObject 协议的类实例。 (请注意,这仍然是早期的测试版,并且在测试版 5 中发生了变化。) 至于接下来的两件事?一次问一个。显示代码。请注意,#2(显示警报)在这 5 个测试版中经历了三处更改。

标签: ios swift swiftui


【解决方案1】:

我找到了答案。如果你想在回调中显示另一个视图,你应该

  1. 创建状态@State var pushActive = false

  2. 当 ViewModel 通知登录成功时,将 pushActive 设置为 true

    func handleSuccessfullLogin() {
        self.pushActive = true
        print("handleSuccessfullLogin")
    }
    
  3. 创建隐藏的NavigationLink 并绑定到该状态

    NavigationLink(destination: 
       ProfileView(viewModel: ProfileViewModelImpl()),
       isActive: self.$pushActive) {
         EmptyView()
    }.hidden()
    

【讨论】:

  • Text("") 为我在布局中添加了额外的空间,将其替换为 EmptyView() 成功了
  • 我正在寻找类似的答案,我必须登录用户并导航到详细信息屏幕。谢谢...
  • 在第3步中,需要为绑定添加一个$...即self.$pushActive
  • 太棒了!一直在寻找这个答案!
  • 谢谢!尽管这很好用,但我认为 Apple 应该更改 SwiftUI 以使其成为可能,而无需借助这种不直观的 hack 以编程方式打开新视图。
【解决方案2】:

我在这里添加了一些 sn-ps,因为我认为它简化了一些事情并使导航链接的重用变得更容易:

1.添加视图导航扩展

extension View {
    func navigatePush(whenTrue toggle: Binding<Bool>) -> some View {
        NavigationLink(
            destination: self,
            isActive: toggle
        ) { EmptyView() }
    }

    func navigatePush<H: Hashable>(when binding: Binding<H>,
                                   matches: H) -> some View {
        NavigationLink(
            destination: self,
            tag: matches,
            selection: Binding<H?>(binding)
        ) { EmptyView() }
    }

    func navigatePush<H: Hashable>(when binding: Binding<H?>,
                                   matches: H) -> some View {
        NavigationLink(
            destination: self,
            tag: matches,
            selection: binding
        ) { EmptyView() }
    }
}

现在,您可以调用任何视图(确保它们(或父级)在导航视图中)

2。休闲使用

struct Example: View {
    @State var toggle = false
    @State var tag = 0

    var body: some View {
        NavigationView {
            VStack(alignment: .center, spacing: 24) {
                Text("toggle pushed me")
                    .navigatePush(whenTrue: $toggle)
                Text("tag pushed me (2)")
                    .navigatePush(when: $tag, matches: 2)
                Text("tag pushed me (4)")
                    .navigatePush(when: $tag, matches: 4)

                Button("toggle") {
                    self.toggle = true
                }

                Button("set tag 2") {
                    self.tag = 2
                }

                Button("set tag 4") {
                    self.tag = 4
                }
            }
        }
    }
}

【讨论】:

    【解决方案3】:

    正如@Bhodan 提到的,你可以通过改变状态来做到这一点

    在 SwiftUI 中使用 EnvironmentObject

    1. 添加 UserData ObservableObject :
    class UserData: ObservableObject, Identifiable {
    
        let id = UUID()
        @Published var firebase_uid: String = ""
        @Published var name: String = ""
        @Published var email: String = ""
        @Published var loggedIn: Bool = false
    }
    

    loggedIn 属性将用于监控用户何时登录或注销

    1. 现在将它作为@EnvironmentObject 添加到 Xcode 中的 SceneDelegate.swift 文件中 这只是使它可以在您的应用中随处访问
    class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    
        var window: UIWindow?
    
    
        func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
            // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
            // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
            // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
    
            // Create the SwiftUI view that provides the window contents.
            let userData = UserData()
            let contentView = ContentView().environmentObject(userData)
    
            // Use a UIHostingController as window root view controller.
            if let windowScene = scene as? UIWindowScene {
                let window = UIWindow(windowScene: windowScene)
                window.rootViewController = UIHostingController(rootView: contentView)
                self.window = window
                window.makeKeyAndVisible()
            }
        }
    

    一旦您对 loggedIn 属性进行任何更改,任何绑定到它的 UI 都会响应真/假值更改

    @Bhodan 提到的只需将其添加到您的视图中,它将响应该更改

    
    struct LoginView: View {
    @EnvironmentObject var userData: UserData
    
    var body: some View {
    NavigationView {
    VStack {
    NavigationLink(destination: ProfileView(), isActive: self.$userData.loggedin) {
        EmptyView()
        }.hidden()
       }
      }
     }
    }
    

    【讨论】:

      【解决方案4】:

      从 beta 5 开始,NavigationLink 是用于以编程方式推送视图的机制。你可以看到它的一个例子here

      【讨论】:

        【解决方案5】:

        解决方法而不创建其他空视图。

        您可以使用 .disabled(true).allowsHitTesting(false) 修饰符来禁用 NavigationLink 上的点击。

        缺点:你失去了默认的按钮点击突出显示。

        NavigationLink(destination: EnterVerificationCodeScreen(), isActive: self.$viewModel.verifyPinIsShowing) {
            Text("Create an account")
        }
        .allowsHitTesting(false) // or .disabled(true) 
        .buttonStyle(ShadowRadiusButtonStyle(type: .dark, height: 38))
        

        【讨论】:

        • verifyPinIsShowing 是带有@State 或简单变量的变量
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-07-21
        • 1970-01-01
        相关资源
        最近更新 更多