SwiftUI 通用导航 link 使用块变量

SwiftUI generic navigation link using block variable

这里我想创建导航代码,我可以做到这一点Swift UIKit。我正在 SwiftUI 中尝试相同的功能,但我的代码遇到了问题。我们如何将 SwiftUI 视图转换为 AnyView.

SwiftUI 中是否有任何其他方法可以实现相同的功能?

非常感谢您的帮助。!!


/// Swift code
public struct Navigator {
    public var onLoginSuccess: (UINavigationController) -> Void = { navigationController in
        navigationController.pushViewController(UIViewController(), animated: true)
    }
}

/// Usage
var router = Navigator()
router.onLoginSuccess = { nav in
    nav.pushViewController(UIViewController(), animated: true)
}


/// SwiftUI  Code
struct Navigator {
    static var onTap: (AnyView) -> Void = { view in
        _ = view.navigate(to: Text("SS"))
    }
}

extension View {
    func navigate<SomeView: View>(to view: SomeView) -> some View {
        modifier(NavigateModifier(destination: view))
    }
}


fileprivate struct NavigateModifier<SomeView: View>: ViewModifier {
    fileprivate let destination: SomeView
    
    fileprivate func body(content: Content) -> some View {
        NavigationView {
            ZStack {
                content
                NavigationLink(destination: destination) {
                    EmptyView()
                }
            }
        }
    }
}

/// Usage 
NavigationView {
    Button("Home") {
        Navigator.onTap(self)
    }
}

这是另一个解决方案代码,可以很好地处理单个目标,但我无法更改目标运行时。 Router.onLogin 应该接受目标视图。

struct ContentView: View {
    var body: some View {
        NavigationView {
            HStack {
                NavigationLink(destination: Router.onLogin) {
                    Text("HOME")
                }
            }
        }
    }
}


struct Router {
    @ViewBuilder
    static var onLogin: some View {
        Text("Hello")
    }
}

这是行不通的,因为在 Navigator onTap return void 中,它不会在任何视图上推送视图。

但是你可以这样做

extension View {
    /// Navigate to a new view.
    /// - Parameters:
    ///   - view: View to navigate to.
    ///   - binding: Active binding
    func navigate<NewView: View>(to view: NewView, when binding: Binding<Bool>) -> some View {
        ZStack {
            self
            NavigationLink(
                destination: view,
                isActive: binding
            ) {
                EmptyView()
            }
        }
    }
}

用法:

struct ContentView: View {
    
    @State private var isNextScreen: Bool = false
    
    var body: some View {
        NavigationView {
            Button("Home") {
                isNextScreen.toggle()
            }.navigate(to: Text("SS"),when: $isNextScreen)
        }
    }
}

更新

正如您在评论中提到的,您需要多个动态目的地。

那你就可以这么用了

查看导航扩展

extension View {
    func navigate(to view: Binding<Navigator?>) -> some View {
        ZStack {
            self
            if let wrappedValue = view.wrappedValue {
                NavigationLink(
                    destination: wrappedValue.navigateView,
                    tag: wrappedValue,
                    selection: view,
                    label: {EmptyView()})
            }
        }
    }
}

创建导航器

enum Navigator: Identifiable {
    case onTap
    case onLogin
    
    var id: Navigator {
        return self
    }
    
    @ViewBuilder
    var navigateView: some View {
        switch self {
        case .onTap:
            Text("SS")
            
        case .onLogin:
            Text("Login View")
        }
    }
}

使用内容查看

struct ContentView: View {
    
    @State private var nextScreen: Navigator? = nil
    
    var body: some View {
        NavigationView {
            VStack{
                Button("Home") {
                    nextScreen = .onTap
                }
                
                Button("Login") {
                    nextScreen = .onLogin
                }
                
            }.navigate(to: $nextScreen)
        }
    }
}

这里我有一个 解决方案 解决这个问题,但不确定它是最好的。我相信我们仍然可以改进这一点。

struct LoginView: View {
    
    var loginAction = Router().onLogin(AnyView(Text("Actual View Wll This")))
    
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: loginAction) {
                    Text("Show View")
                }
                NavigationLink(destination: AnyView(Text("Detail"))) {
                    Text("Show Detail View")
                }
            }
        }
    }
}

extension LoginView {
    struct Router {
        var onLogin:(AnyView) -> AnyView = { links in
            return AnyView(links)
        }
    }
}


/// Now I can change the 'Destination' for 'loginAction' in a scene and where ever I want.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    var contentView = LoginView()
    contentView.loginAction =  AnyView(Text("Changed Route to here"))
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: contentView)
        self.window = window
        window.makeKeyAndVisible()
    }
}

// 经过一些改进

struct LoginView: View {
    var loginAction = Router().onLogin(Text("Actual Detail View"))

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: loginAction) {
                    Text("Show View")
                }
            }
        }
    }
}

extension LoginView {
    struct Router<Destination: View> {
        var onLogin:(Destination) -> Destination = { links in
            return links
        }
    }
}

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        
        var contentView = LoginView()
        contentView.loginAction =  LoginView.Router().onLogin(Text("Changed Route here .!!"))
        
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }