【问题标题】:NavigationLink moving to intended view then reverting back to previous viewNavigationLink 移动到预期视图,然后恢复到以前的视图
【发布时间】:2022-11-11 01:50:08
【问题描述】:

我正在开发一个新的社交媒体应用程序,并且我的导航代码有问题。

用户填写注册表单后,我希望提示他们上传个人资料图片。我遇到的问题是它显示了半秒钟的预期视图,然后又回到了注册视图。

我有一个处理 UI 的 RegistrationView 和一个负责服务器端通信的 AuthViewModel。本质上是当用户完成输入信息并点击按钮时。 AuthViewModel 接管并将信息发送到 firebase,然后触发 Bool 为真。

然后,我在 RegistrationView 上有一个 NagivationLink 来监听该布尔值,当它为真时,会更改 UI 上的视图。这是代码。

NavigationLink(destination: ProfilePhotoSelectorView(), isActive: $viewModel.didAuthenticateUser, label:{} )

XCode 吐槽它在 iOS 16 中已被弃用,并转移到他们开发的 NavigationStack 系统。但是,对于我能看到的每一个指南,我都无法让它发挥作用。我唯一可以让它工作的是通过上面的代码并返回这个 UI 故障。

这是 RegistrationView 的完整代码

import SwiftUI

struct RegistrationView: View {
    @State private var email = ""
    @State private var username = ""
    @State private var fullName = ""
    @State private var password = ""
    @State private var isVerified = false

    @Environment(\.presentationMode) var presentationMode
    @EnvironmentObject var viewModel: AuthViewModel
    
    var body: some View {
        VStack{

                NavigationLink(destination: ProfilePhotoSelectorView(), isActive: $viewModel.didAuthenticateUser, label:{} )
            
            AuthHeaderView(title1: "Get Started.", title2: "Create Your Account.")
            VStack(spacing: 40) {
                CustomInputFields(imageName: "envelope", placeholderText: "Email", isSecureField: false, text: $email)
                
                CustomInputFields(imageName: "person", placeholderText: "Username", isSecureField: false, text: $username)
                
                CustomInputFields(imageName: "person", placeholderText: "Full Name", isSecureField: false, text: $fullName)
                
                CustomInputFields(imageName: "lock", placeholderText: "Password", isSecureField: true, text: $password)
            }
            .padding(32)
            
            
            Button {
                viewModel.register(withEmail: email, password: password, fullname: fullName, username: username, isVerified: isVerified)
            } label: {
                Text("Sign Up")
                    .font(.headline)
                    .foregroundColor(.white)
                    .frame(width: 340, height: 50)
                    .background(Color("AppGreen"))
                    .clipShape(Capsule())
                    .padding()
            }
            .shadow(color: .gray.opacity(0.5), radius: 10, x:0, y:0)
            
            Spacer()
            
            Button {
                presentationMode.wrappedValue.dismiss()
            } label: {
                HStack {
                    Text("Already Have And Account?")
                        .font(.caption)
                    
                    Text("Sign In")
                        .font(.footnote)
                        .fontWeight(.semibold)
                }
            }
            .padding(.bottom, 32)
            .foregroundColor(Color("AppGreen"))

        }
        .ignoresSafeArea()
        .preferredColorScheme(.dark)
    }
}

struct RegistrationView_Previews: PreviewProvider {
    static var previews: some View {
        RegistrationView()
    }
}

这是 AuthViewModel 的完整代码

import SwiftUI
import Firebase

class AuthViewModel: ObservableObject {
    @Published var userSession: Firebase.User?
    @Published var didAuthenticateUser = false
    
    init() {
        self.userSession = Auth.auth().currentUser
        
        print("DEBUG: User session is \(String(describing: self.userSession?.uid))")
    }
    
    func login(withEmail email: String, password: String){
        Auth.auth().signIn(withEmail: email, password: password) { result, error in
            if let error = error {
                print("DEBUG: Failed to sign in with error\(error.localizedDescription)")
                return
            }
            
            guard let user = result?.user else { return }
            self.userSession = user
            print("Did log user in")
        }
    }
    
    func register(withEmail email: String, password: String, fullname: String, username: String, isVerified: Bool){
        Auth.auth().createUser(withEmail: email, password: password) { result, error in
            if let error = error {
                print("DEBUG: Failed to register with error\(error.localizedDescription)")
                return
            }
            
            guard let user = result?.user else { return }
            
            print("DEBUG: Registerd User Succesfully")
            
            let data = ["email": email, "username" :username.lowercased(), "fullname": fullname, "isVerified": isVerified, "uid": user.uid]
            
            Firestore.firestore().collection("users")
                .document(user.uid)
                .setData(data) { _ in
                    self.didAuthenticateUser = true
                }
        }
    }
    
    func signOut() {
        userSession = nil
        try? Auth.auth().signOut()
    }
}

这是 ProfilePhotoSelectorView 的代码

import SwiftUI

struct ProfilePhotoSelectorView: View {
    var body: some View {
        VStack {
            AuthHeaderView(title1: "Account Creation:", title2: "Add A Profile Picture")
            
            Button {
                print("Pick Photo Here")
            } label: {
                VStack{
                    Image("PhotoIcon")
                        .resizable()
                        .renderingMode(.template)
                        .frame(width: 180, height: 180)
                        .scaledToFill()
                        .padding(.top, 44)
                        .foregroundColor(Color("AppGreen"))
                    
                    Text("Tap To Add Photo")
                        .font(.title3).bold()
                        .padding(.top, 10)
                        .foregroundColor(Color("AppGreen"))
                }
            }

            
            Spacer()
        }
        .ignoresSafeArea()
        .preferredColorScheme(.dark)
    }
}

struct ProfilePhotoSelectorView_Previews: PreviewProvider {
    static var previews: some View {
        ProfilePhotoSelectorView()
    }
}

尝试了新 NavigationStack 的所有变体,并尝试了其他一些按钮代码,看看我是否可以从那里触发它。无分辨率

【问题讨论】:

  • 您可以为ProfilePhotoSelectorView 添加您的代码吗?此外,您能否检查您是否正在从其他任何地方更改 viewModel.didAuthenticateUser 的值?
  • @WhiteSpidy。添加了代码。在其他地方似乎不是viewmode.didAuthencateUser 的任何其他实例

标签: swift firebase user-interface swiftui navigation


【解决方案1】:

不推荐以这种方式使用NavigationLink,所以我并不惊讶它会导致错误行为。由于您的RegistrationView 未放置在NavigationView(已弃用)或NavigationStack 中这一事实加剧了这种情况,因为这些视图提供了导航链接的大部分功能。

正如你所说,NavigationLink 的这种用法已被弃用。在我看来,isActive 属性总是有点模棱两可(据我了解,它并不是导航链接的“激活器”,而是一种读取链接是否处于活动状态的方式)。呈现导航链接的新方式(使用.navigationDestination)要好得多。

使用布尔属性呈现视图

您本质上想要的是在布尔属性切换为 true 时呈现 ProfilePhotoSelectorView。这是 SwiftUi 中常见的范例,有很多方法可以做到这一点,例如 .sheet(isPresented:content:).popover(isPresented:content:)。请注意,两种方法中的 isPresented 参数都是布尔属性。使用.sheet,例如:

struct RegistrationView: View {
    // ...
    @EnvironmentObject var viewModel: AuthViewModel
    
    var body: some View {
        VStack {
            // ...
        }
        // Presents the photo selector view when `didAuthenticateUser` is true
        .sheet(isPresented: $viewModel.didAuthenticateUser) {
            ProfilePhotoSelectorView()
        }
    }
}

向导航树添加新视图

如果您坚持使用导航链接(即您真的想要ProfilePhotoSelectorView 成为导航树中的一个节点),您将不得不学习使用新的NavigationStack 并将视图附加到路径上。这将需要一些重组(可能需要您阅读一些资料;herehere 是很好的起点)。视图模型将是最有可能控制堆栈的地方,尽管您最终可能希望创建一个专用的视图模型。这是一个简单的例子:

struct ContentView: View {
    @StateObject var viewModel = AuthViewModel()
    
    var body: some View {
        NavigationStack(path: $viewModel.navigationPath) {
            RegistrationView()
                .environmentObject(viewModel)
                
                .navigationDestination(for: RegistrationScreen.self) { screen in
                    switch screen {
                    case .photoSelection:
                        ProfilePhotoSelectorView()
                    }
                }
        }
    }
}

class AuthViewModel: ObservableObject {
    // ...

    // A new enum that defines the various types of possible views in the navigation stack
    enum RegistrationScreen: Hashable {
        case photoSelection
    }

    // The navigation path
    @Published var navigationPath: [RegistrationScreen] = []

    // Example usages of the navigation path. These functions show how to programmatically control the navigation stack
    func showPhotoSelectionScreen() {
        self.navigationPath.append(.photoSelection)
    }

    func goToRootOfNavigation() {
        self.navigationPath = []
    }
}

【讨论】:

  • 非常感谢! .sheet 方法按预期工作。不是一个完美的全屏,但它现在可以工作。希望不会随着应用程序的扩展而搞砸其余的入职培训。
【解决方案2】:

如果我正确理解你的问题,你想使用NavigationStack, 但它不适合你。

有很多缺失的部分,但这是我尝试使用NavigationStack 触发目的地,给定viewModel.didAuthenticateUser 的变化。

struct ContentView: View {
    @StateObject var viewModel = AuthViewModel()
    
    var body: some View {
        NavigationStack(path: $viewModel.didAuthenticateUser) {  // <-- here
            RegistrationView()
                .environmentObject(viewModel)
        }
    }
}

struct RegistrationView: View {
    @State private var email = ""
    @State private var username = ""
    @State private var fullName = ""
    @State private var password = ""
    @State private var isVerified = false
    
    @Environment(.presentationMode) var presentationMode
    @EnvironmentObject var viewModel: AuthViewModel
    
    var body: some View {
        VStack{
            AuthHeaderView(title1: "Get Started.", title2: "Create Your Account.")
            VStack(spacing: 40) {
                CustomInputFields(imageName: "envelope", placeholderText: "Email", isSecureField: false, text: $email)

                CustomInputFields(imageName: "person", placeholderText: "Username", isSecureField: false, text: $username)

                CustomInputFields(imageName: "person", placeholderText: "Full Name", isSecureField: false, text: $fullName)

                CustomInputFields(imageName: "lock", placeholderText: "Password", isSecureField: true, text: $password)
            }
            .padding(32)

            Button {
                viewModel.register(withEmail: email, password: password, fullname: fullName, username: username, isVerified: isVerified)
            } label: {
                Text("Sign Up")
                    .font(.headline)
                    .foregroundColor(.white)
                    .frame(width: 340, height: 50)
                    .background(Color("AppGreen"))
                    .clipShape(Capsule())
                    .padding()
            }
            .shadow(color: .gray.opacity(0.5), radius: 10, x:0, y:0)
            
            Spacer()
            
            Button {
                presentationMode.wrappedValue.dismiss()
            } label: {
                HStack {
                    Text("Already Have And Account?")
                        .font(.caption)
                    
                    Text("Sign In")
                        .font(.footnote)
                        .fontWeight(.semibold)
                }
            }
            .padding(.bottom, 32)
            .foregroundColor(Color("AppGreen"))
            
        }
        .navigationDestination(for: Bool.self) { _ in  // <-- here
            ProfilePhotoSelectorView()
        }
        .ignoresSafeArea()
        .preferredColorScheme(.dark)
    }
}

class AuthViewModel: ObservableObject {
    @Published var userSession: Firebase.User?
    @Published var didAuthenticateUser: [Bool] = [] // <-- here
    
    init() {
        self.userSession = Auth.auth().currentUser
        
        print("DEBUG: User session is (String(describing: self.userSession?.uid))")
    }
    
    func login(withEmail email: String, password: String){
        Auth.auth().signIn(withEmail: email, password: password) { result, error in
            if let error = error {
                print("DEBUG: Failed to sign in with error(error.localizedDescription)")
                return
            }

            guard let user = result?.user else { return }
            self.userSession = user
            print("Did log user in")
        }
    }
    
    func register(withEmail email: String, password: String, fullname: String, username: String, isVerified: Bool){

        Auth.auth().createUser(withEmail: email, password: password) { result, error in
            if let error = error {
                print("DEBUG: Failed to register with error(error.localizedDescription)")
                return
            }

            guard let user = result?.user else { return }

            print("DEBUG: Registerd User Succesfully")

            let data = ["email": email, "username" :username.lowercased(), "fullname": fullname, "isVerified": isVerified, "uid": user.uid]

            Firestore.firestore().collection("users")
                .document(user.uid)
                .setData(data) { _ in
                    self.didAuthenticateUser = [true]  // <-- here
                }
        }
    }
    
    func signOut() {
        userSession = nil
        try? Auth.auth().signOut()
    }
}


struct ProfilePhotoSelectorView: View {
    var body: some View {
        VStack {
            AuthHeaderView(title1: "Account Creation:", title2: "Add A Profile Picture")

            Button {
                print("Pick Photo Here")
            } label: {
                VStack{
                    Image(systemName: "globe")
                        .resizable()
                        .renderingMode(.template)
                        .frame(width: 180, height: 180)
                        .scaledToFill()
                        .padding(.top, 44)
                        .foregroundColor(Color("AppGreen"))
                    
                    Text("Tap To Add Photo")
                        .font(.title3).bold()
                        .padding(.top, 10)
                        .foregroundColor(Color("AppGreen"))
                }
            }
            Spacer()
        }
        .ignoresSafeArea()
        .preferredColorScheme(.dark)
    }
}

【讨论】:

    【解决方案3】:

    除非您将链接标记为isDetail(false) 或使用.navigationStyle(.stack),否则您只能拥有一级NavigationLinks。

    原因是因为在横向中它使用拆分视图,并且链接替换了右侧的大详细信息窗格。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-18
      • 1970-01-01
      相关资源
      最近更新 更多