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

Ale*_*lex 7 generics protocols view swift swiftui

我正在实现一个非常自定义的NavigationLink MenuItem,它想在整个项目中重复使用。这是一个符合View和实现的结构,var body : some View其中包含NavigationLink。我需要以某种方式将将要呈现的视图存储NavigationLink在主体中,MenuItem但尚未这样做。

destinationViewMenuItem的主体中定义为,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...
    }
}
Run Code Online (Sandbox Code Playgroud)

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

第二次尝试:

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...
    }
}
Run Code Online (Sandbox Code Playgroud)

->错误:无法将类型“ V”的值分配给类型“ some View”。

最后尝试:

struct MenuItem: View {
    private var destinationView: some View

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

    var body : some View {
        // Here I'm passing destinationView to NavigationLink...
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

ram*_*nok 48

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

struct ContainerView<Content: View>: View {
    @ViewBuilder var content: Content
    
    var body: some View {
        content
    }
}
Run Code Online (Sandbox Code Playgroud)

这不仅允许您将简单的Views 放入其中,而且由于@ViewBuilder, useif-elseswitch-caseblocks :

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")
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

奖励: 如果你想要一个贪婪的容器,它将要求所有可能的空间(与上面的容器相反,它只要求其子视图所需的空间),它是:

struct GreedyContainerView<Content: View>: View {
    let content: Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content()
    }
    
    var body: some View {
        Color.clear
            .overlay(content)
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢:) 嗯,“Content”只是一个通用属性的名称,它不应该导致某些东西工作或不工作。我刚刚检查了这个解决方案,它在 macOS 上完美运行,无需任何更改 (2认同)

Cli*_*rum 11

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

struct ContainerView<Content: View>: View {
  @ViewBuilder var content: Content
    
  var body: some View {
    content
  }
}
Run Code Online (Sandbox Code Playgroud)

然后继续这样使用:

ContainerView{
  ...
}
Run Code Online (Sandbox Code Playgroud)


rra*_*ael 8

您应该将通用参数作为以下内容的一部分MenuItem

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

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

    var body : some View {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 多谢!我对泛型还是新手,所以很高兴看到它实际上并不那么难。所以我们基本上是在说“嘿,有一个包含符合“View”的对象的“MenuItem”。我们将其类型称为“Content”。“destinationView”就是这种类型,我们在“init()”期间需要它”。稍后阅读本文的任何人请注意:您可以删除 init(),因为它是为您合成的;D (4认同)

Nat*_*Day 8

Apple使用函数生成器的方式是,有一个预定义的函数称为ViewBuilder,它使MenuItem的init方法的最后一个或唯一一个参数成为this。

...., @ViewBuilder builder: @escaping () -> Content)
Run Code Online (Sandbox Code Playgroud)

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

  let                 viewBuilder: () -> Content
Run Code Online (Sandbox Code Playgroud)

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

HStack {
    viewBuilder()
}
Run Code Online (Sandbox Code Playgroud)

那你就可以

MenuItem {
   Image("myImage")
   Text("My Text")
}
Run Code Online (Sandbox Code Playgroud)

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


Geo*_*e_E 8

我真的很努力地让我的工作延伸到View. 有关如何调用它的完整详细信息请参见此处

(使用泛型)的扩展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()
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 杰出的。这是视图定制的优雅实现。它还将导航栏联合隐藏在一处,而不是在所有视图中重复它,并进行自定义,例如在这种情况下,当包含在列表中时隐藏公开指示器。 (2认同)

Mic*_*sen 5

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

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")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用方法:

struct ContentView: View {

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

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Run Code Online (Sandbox Code Playgroud)


Yar*_*arm 5

您可以将 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")
    }
}
Run Code Online (Sandbox Code Playgroud)