23
votes

Push View par programmation dans le rappel, SwiftUI

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:

  • Poussez un autre contrôleur si la connexion est réussie
  • Afficher l'alerte si l'erreur s'est produite

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


1 commentaires

Vous avez raison, si vous avez un projet SwiftUI , à SwiftUI , vous ne devriez utiliser un UIViewController 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 protocole ObservableObject . (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.


5 Réponses :


2
votes

Depuis la version bêta 5, NavigationLink est le mécanisme utilisé pour pousser les vues par programmation. Vous pouvez en voir un exempleici .


0 commentaires

38
votes

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


2 commentaires

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



0
votes

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


1 commentaires

verifyPinIsShowing est une variable avec @State ou une variable simple



2
votes

comme @Bhodan l'a mentionné, vous pouvez le faire en changeant d'état

Utilisation de EnvironmentObject avec SwiftUI

  1. Ajouter UserData ObservableObject:
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

  1. Maintenant, ajoutez-le en tant que @EnvironmentObject dans votre fichier SceneDelegate.swift dans Xcode, cela le rend accessible partout dans votre application
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()
        }
    }

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
}


0 commentaires

0
votes

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


0 commentaires