在回调中以编程方式推送视图,SwiftUI

Push View programmatically in callback, SwiftUI

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

发出事件

ViewModel:

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,登录成功不知道怎么推送另一个controller

此外,对于如何以更好的方式实现我想要的任何建议,我将不胜感激。谢谢。

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

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

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

  2. 当ViewModel通知登录成功时设置pushActivetrue

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

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

解决方法,而不创建额外 个空视图。

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

Disadvantage: You loose default button tap highlighting.

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

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

在 Swift 中使用 EnvironmentObjectUI

  1. 添加用户数据 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 都会响应 true/false 值更改

正如@Bhodan 提到的那样,只需将其添加到您的视图中,它就会响应该更改


struct LoginView: View {
@EnvironmentObject var userData: UserData

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

我在这里添加了一些片段,因为我认为它简化了一些事情并使重用导航链接更容易:

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

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

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
                }
            }
        }
    }
}

CleanUI 非常简单。

import SwiftUI
import CleanUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            Button(action: {
                CUNavigation.pushToSwiftUiView(YOUR_VIEW_HERE)
            }){
                Text("Push To SwiftUI View")
            }
        }
    }
}