How to pass one SwiftUI View as a variable to another View struct

后端 未结 6 1024
孤城傲影
孤城傲影 2020-12-30 19:24

I\'m implementing a very custom NavigationLink called MenuItem and would like to reuse it across the project. It\'s a struct that conforms to Vie

相关标签:
6条回答
  • 2020-12-30 19:41

    To sum up everything I read here and the solution which worked for me and on iOS14:

    struct ContainerView<Content: View>: View {
        let content: Content
    
        init(@ViewBuilder content: @escaping () -> Content) {
            self.content = content()
        }
        
        var body: some View {
            content
        }
    }
    

    This not only allows you to put simple Views inside, but also, thanks to @ViewBuilder, use if-else and switch-case blocks:

    struct SimpleView: View {
        var body: some View {
            ContainerView {
                Text("SimpleView Text")
            }
        }
    }
    
    struct IfElseView: View {
        var flag = true
        
        var body: some View {
            ContainerView {
                if flag {
                    Text("True text")
                } else {
                    Text("False text")
                }
            }
        }
    }
    
    struct SwitchCaseView: View {
        var condition = 1
        
        var body: some View {
            ContainerView {
                switch condition {
                case 1:
                    Text("One")
                case 2:
                    Text("Two")
                default:
                    Text("Default")
                }
            }
        }
    }
    

    Bonus: If you want a greedy container, which will claim all the possible space (in contrary to the container above which claims only the space needed for its subviews) here's it:

    struct GreedyContainerView<Content: View>: View {
        let content: Content
    
        init(@ViewBuilder content: @escaping () -> Content) {
            self.content = content()
        }
        
        var body: some View {
            Color.clear
                .overlay(content)
        }
    }
    
    0 讨论(0)
  • 2020-12-30 19:53

    You can create your custom view like this:

    struct ENavigationView<Content: View>: View {
    
        let viewBuilder: () -> Content
    
        var body: some View {
            NavigationView {
                VStack {
                    viewBuilder()
                        .navigationBarTitle("My App")
                }
            }
        }
    
    }
    
    struct ENavigationView_Previews: PreviewProvider {
        static var previews: some View {
            ENavigationView {
                Text("Preview")
            }
        }
    }
    

    Using:

    struct ContentView: View {
    
        var body: some View {
            ENavigationView {
                Text("My Text")
            }
        }
    
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
    0 讨论(0)
  • 2020-12-30 19:55

    I really struggled to make mine work for an extension of View. Full details about how to call it are seen here.

    The extension for View (using generics) - remember to import SwiftUI:

    extension View {
    
        /// Navigate to a new view.
        /// - Parameters:
        ///   - view: View to navigate to.
        ///   - binding: Only navigates when this condition is `true`.
        func navigate<SomeView: View>(to view: SomeView, when binding: Binding<Bool>) -> some View {
            modifier(NavigateModifier(destination: view, binding: binding))
        }
    }
    
    
    // MARK: - NavigateModifier
    fileprivate struct NavigateModifier<SomeView: View>: ViewModifier {
    
        // MARK: Private properties
        fileprivate let destination: SomeView
        @Binding fileprivate var binding: Bool
    
    
        // MARK: - View body
        fileprivate func body(content: Content) -> some View {
            NavigationView {
                ZStack {
                    content
                        .navigationBarTitle("")
                        .navigationBarHidden(true)
                    NavigationLink(destination: destination
                        .navigationBarTitle("")
                        .navigationBarHidden(true),
                                   isActive: $binding) {
                        EmptyView()
                    }
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-30 19:58

    The way Apple does it is using function builders. There is a predefined one called ViewBuilder. Make it the last argument, or only argument, of your init method for MenuItem, like so:

    ..., @ViewBuilder builder: @escaping () -> Content)
    

    Assign it to a property defined something like this:

    let viewBuilder: () -> Content
    

    Then, where you want to diplay your passed-in views, just call the function like this:

    HStack {
        viewBuilder()
    }
    

    You will be able to use your new view like this:

    MenuItem {
       Image("myImage")
       Text("My Text")
    }
    

    This will let you pass up to 10 views and use if conditions etc. though if you want it to be more restrictive you will have to define your own function builder. I haven't done that so you will have to google that.

    0 讨论(0)
  • 2020-12-30 20:04

    You should make the generic parameter part of MenuItem:

    struct MenuItem<Content: View>: View {
        private var destinationView: Content
    
        init(destinationView: Content) {
            self.destinationView = destinationView
        }
    
        var body : some View {
            // ...
        }
    }
    
    0 讨论(0)
  • 2020-12-30 20:04

    You can pass a NavigationLink (or any other view widget) as a variable to a subview as follows:

    import SwiftUI
    
    struct ParentView: View {
        var body: some View {
            NavigationView{
    
                VStack(spacing: 8){
    
                    ChildView(destinationView: Text("View1"), title: "1st")
                    ChildView(destinationView: Text("View2"), title: "2nd")
                    ChildView(destinationView: ThirdView(), title: "3rd")
                    Spacer()
                }
                .padding(.all)
                .navigationBarTitle("NavigationLinks")
            }
        }
    }
    
    struct ChildView<Content: View>: View {
        var destinationView: Content
        var title: String
    
        init(destinationView: Content,  title: String) {
            self.destinationView = destinationView
            self.title = title
        }
    
        var body: some View {
            NavigationLink(destination: destinationView){
                Text("This item opens the \(title) view").foregroundColor(Color.black)
            }
        }
    }
    
    struct ThirdView: View {
        var body: some View {
            VStack(spacing: 8){
    
                ChildView(destinationView: Text("View1"), title: "1st")
                ChildView(destinationView: Text("View2"), title: "2nd")
                ChildView(destinationView: ThirdView(), title: "3rd")
                Spacer()
            }
            .padding(.all)
            .navigationBarTitle("NavigationLinks")
        }
    }
    
    0 讨论(0)
提交回复
热议问题