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

时间:2019-08-01 18:55:59

标签: ios swift 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,我不知道如何实现以下目标:

  • 如果登录成功,则推送另一个控制器
  • 如果发生错误,则显示警报

此外,我希望您能以更好的方式实现自己想要的任何建议。谢谢。

更新1:我能够显示警报,但仍然找不到如何在viewModel的回调中推送另一个视图

5 个答案:

答案 0 :(得分:15)

我找到了答案。如果要在回调中显示其他视图,则应

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) {
    Text("")
  }.hidden()

答案 1 :(得分:2)

正如@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. 现在将其添加为Xcode中@EnvironmentObject文件中的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的任何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()
   }
  }
 }
}

答案 2 :(得分:1)

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

答案 3 :(得分:0)

解决方法,而无需创建其他空白视图。

您可以使用.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))

答案 4 :(得分:0)

我在此处添加一些代码段,因为我认为它可以简化某些操作并使重用导航链接更容易:

1。添加查看导航扩展

ComputeHash(input[], N)
For hash-input input[] of length N bytes (8N bits) and a random secret key K of 320 bits
Result = 0;
For each bit b in input[] {
if (b == 1) then Result ^= (left-most 32 bits of K);
shift K left 1 bit position;
}
return Result;

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

2。休闲使用

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