如何将一个SwiftUI View作为变量传递给另一个View结构

时间:2019-07-08 16:10:42

标签: swift generics view protocols swiftui

我正在实现名为MenuItem非常自定义NavigationLink,并希望在整个项目中重复使用它。这是一个符合View并实现包含var body : some View的{​​{1}}的结构。 我需要以某种方式将NavigationLink所呈现的视图存储在NavigationLink的正文中,但尚未这样做。

我在MenuItem的正文中将destinationView定义为MenuItem,并尝试了两个初始化方法:

这似乎太简单了:

some View

->错误::协议“视图”仅具有通用或相关类型要求,因此只能用作通用约束。

第二次尝试:

struct MenuItem: View {
    private var destinationView: some View

    init(destinationView: View) {
        self.destinationView = destinationView
    }

    var body : some View {
        // Here I'm passing destinationView to NavigationLink...
    }
}

->错误:无法将类型为“ V”的值分配为类型为“某些视图”。

最终尝试:

struct MenuItem: View {
    private var destinationView: some View

    init<V>(destinationView: V) where V: View {
        self.destinationView = destinationView
    }

    var body : some View {
        // Here I'm passing destinationView to NavigationLink...
    }
}

->错误::无法将“视图”类型的值分配给“某些视图”。

我希望有人能帮助我。如果NavigationLink可以接受某些View作为参数,则必须有一种方法。 谢谢; D

7 个答案:

答案 0 :(得分:13)

总结一下,我在这里阅读的所有内容以及适用于我和 iOS14 的解决方案:

<nav id="my-menu">
    <ul>
        <li><a href="/">Home</a></li>
        <li><a href='/'>About us</span>
            <ul>
                <li><a href="/about/history">History</a></li>
                <li><a href="/about/team">The team</a></li>
                <li><a href="/about/address">Our address</a></li>
            </ul>
        </li>
        <li><a href="/contact">Contact</a></li>
    </ul>
</nav>

这不仅允许您将简单的struct ContainerView<Content: View>: View { let content: Content init(@ViewBuilder content: @escaping () -> Content) { self.content = content() } var body: some View { // Do something with `content` } } 放入其中,而且还得益于View,使用@ViewBuilderif-else块:

switch-case

答案 1 :(得分:6)

您可以这样创建自定义视图:

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

使用:

struct ContentView: View {

    var body: some View {
        ENavigationView {
            Text("My Text")
        }
    }

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

答案 2 :(得分:1)

您应该将通用参数作为MenuItem的一部分:

struct MenuItem<Content: View>: View {
    private var destinationView: Content

    init(destinationView: Content) {
        self.destinationView = destinationView
    }

    var body : some View {
        // ...
    }
}

答案 3 :(得分:1)

您可以按如下所示将NavigationLink(或任何其他视图小部件)作为变量传递给子视图:

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

答案 4 :(得分:1)

我真的很难使我的作品扩展为View。请参见here。有关如何调用它的完整详细信息。

View的扩展名(使用泛型)-请记住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()
                }
            }
        }
    }
}

答案 5 :(得分:1)

接受的答案很好也很简单。 iOS 14 + macOS 11 的语法变得更加清晰:

struct ContainerView<Content: View>: View {
  @ViewBuilder var content: Content
    
  var body: some View {
    content
  }
}

然后继续这样使用:

ContainerView{
  ...
}

答案 6 :(得分:0)

Apple使用函数构建器的方式是,有一个预定义的视图构建器,称为ViewBuilder,它使MenuItem的init方法成为您的最后一个参数,或仅将其作为参数

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

将其分配给定义如下的属性

  let                 viewBuilder: () -> Content

然后要在其中输出View的地方,只需调用类似的函数

HStack {
    viewBuilder()
}

那你就可以做

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

这将使您最多传递10个视图并在条件等条件下使用,因此,如果您希望限制更多,则必须定义自己的函数生成器。我还没有这样做,所以您将不得不在Google上搜索。