Il me semble qu'Apple nous encourage à renoncer à utiliser UIViewController
dans SwiftUI, mais sans utiliser les commandes d'affichage, je me sens un peu impuissant. Ce que je voudrais, c'est pouvoir implémenter une sorte de ViewModel
qui émettra des événements vers View
.
VoirModèle :
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 } }
Vue :
public protocol LoginViewModel: ViewModel { var onError: PassthroughSubject<Error, Never> { get } var onSuccessLogin: PassthroughSubject<Void, Never> { get } }
En utilisant SwiftUI, je ne sais pas comment implémenter ce qui suit:
En outre, j'apprécierais tout conseil sur la façon de mettre en œuvre ce que je veux d'une meilleure manière. Merci.
Mise à jour 1: J'ai pu afficher une alerte, mais je ne trouve toujours pas comment pousser une autre vue dans le rappel de viewModel
5 Réponses :
Depuis la version bêta 5, NavigationLink est le mécanisme utilisé pour pousser les vues par programmation. Vous pouvez en voir un exempleici .
J'ai trouvé la réponse. Si vous souhaitez afficher une autre vue lors du rappel, vous devez
1) Créer l'état @State var pushActive = false
2) Lorsque ViewModel notifie que la connexion est réussie, définissez pushActive
sur true
NavigationLink(destination: ProfileView(viewModel: ProfileViewModelImpl()), isActive: self.pushActive) { Text("") }.hidden()
3) Créer un NavigationLink
caché et lier à cet état
func handleSuccessfullLogin() { self.pushActive = true print("handleSuccessfullLogin") }
Text("")
ajouté un espace supplémentaire dans la mise en page pour moi, le remplacer par EmptyView()
fait l'affaire
Je cherchais la réponse similaire où je devais me connecter à l'utilisateur et accéder à l'écran de détail. Merci...
Solution de contournement sans créer de vues vides supplémentaires .
Vous pouvez utiliser les .disabled(true)
ou .allowsHitTesting(false)
pour désactiver les taps sur NavigationLink.
Inconvénient: vous perdez la mise en évidence du bouton par défaut.
NavigationLink(destination: EnterVerificationCodeScreen(), isActive: self.$viewModel.verifyPinIsShowing) { Text("Create an account") } .allowsHitTesting(false) // or .disabled(true) .buttonStyle(ShadowRadiusButtonStyle(type: .dark, height: 38))
verifyPinIsShowing est une variable avec @State ou une variable simple
comme @Bhodan l'a mentionné, vous pouvez le faire en changeant d'état
Utilisation de EnvironmentObject avec SwiftUI
struct LoginView: View { @EnvironmentObject var userData: UserData var body: some View { NavigationView { VStack { NavigationLink(destination: ProfileView(), isActive: self.$userData.loggedin) { EmptyView() }.hidden() } } } }
la propriété loggedIn
sera utilisée pour surveiller quand un changement dans l'utilisateur se connecte ou se déconnecte
@EnvironmentObject
dans votre fichier SceneDelegate.swift
dans Xcode, cela le rend accessible partout dans votre applicationclass 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() } }
Une fois que vous apportez une modification à la propriété loggedIn
, toute interface utilisateur qui lui est liée répondra au changement de valeur vrai / faux
comme @Bhodan l'a mentionné, ajoutez simplement ceci à votre vue et il répondra à ce changement
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 }
J'ajoute quelques extraits ici car je pense que cela simplifie certaines choses et facilite la réutilisation des liens de navigation:
1. Ajouter des extensions de navigation de vue
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 } } } } }
Maintenant, vous pouvez appeler sur n'importe quelle vue (assurez-vous qu'ils (ou un parent) sont dans une vue de navigation)
2. Utilisation à loisir
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() } } }
Vous avez raison, si vous avez un projet
SwiftUI
, àSwiftUI
, vous ne devriez utiliser unUIViewController
si vous avez besoin d'utiliser quelque chose comme des délégués ou quelque chose qui n'est pas (encore) disponible dans SwiftUI natif. Commençons à la case départ; votre modèle doit piloter les vues, pas l'inverse. il devrait probablement s'agir d'une instance de classe conforme au protocoleObservableObject
. (Attention, ceci est encore au début de la bêta, et a changé en bêta 5.) Quant aux deux choses suivantes? Demandez-en un à la fois. Afficher le code. Sachez que # 2 (show alert) a subi TROIS changements sur ces 5 bêtas.