如何将一个 SwiftUI View 作为变量传递给另一个 View 结构
How to pass one SwiftUI View as a variable to another View struct
我正在实施一个名为 MenuItem
非常 的自定义 NavigationLink,并希望在整个项目中重复使用它。它是一个符合 View
并实现 var body : some View
的结构,其中包含一个 NavigationLink
。
我需要以某种方式将 NavigationLink
应呈现的视图存储在 MenuItem
的正文中,但尚未这样做。
我在 MenuItem
的正文中将 destinationView
定义为 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...
}
}
-->错误:协议'View'只能用作泛型约束,因为它有Self或关联类型要求。
第二次尝试:
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...
}
}
--> 错误: 无法将类型 '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...
}
}
--> 错误: 无法将类型 'View' 的值分配给类型 'some View'.
我希望有人能帮助我。如果 NavigationLink 可以接受一些 View 作为参数,则必须有一种方法。
谢谢 ;D
您应该将通用参数设为 MenuItem
的一部分:
struct MenuItem<Content: View>: View {
private var destinationView: Content
init(destinationView: Content) {
self.destinationView = destinationView
}
var body : some View {
// ...
}
}
Apple 的做法是使用函数生成器。有一个预定义的 ViewBuilder
。将它设为 MenuItem
的 init
方法的最后一个参数,或唯一的参数,如下所示:
..., @ViewBuilder builder: @escaping () -> Content)
将其分配给 属性 定义如下:
let viewBuilder: () -> Content
然后,在您想显示 passed-in 视图的地方,只需像这样调用函数:
HStack {
viewBuilder()
}
您将能够像这样使用您的新视图:
MenuItem {
Image("myImage")
Text("My Text")
}
这将允许您传递最多 10 个视图并使用 if
条件等。但如果您希望它更具限制性,则必须定义您自己的函数构建器。我还没有这样做,所以你将不得不 google 那。
您可以像这样创建自定义视图:
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()
}
}
您可以将 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")
}
}
为了 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()
}
}
}
}
}
总结一下我在这里阅读的所有内容以及对我有用的解决方案:
struct ContainerView<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
content
}
}
这不仅允许您在里面放置简单的 View
s,而且,感谢 @ViewBuilder
,使用 if-else
和 switch-case
块:
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")
}
}
}
}
奖金:
如果你想要一个贪婪的容器,它将要求所有可能的 space (与上面的容器相反,它只要求其子视图所需的 space )这里是:
struct GreedyContainerView<Content: View>: View {
@ViewBuilder let content: Content
var body: some View {
content
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
如果您需要在视图中使用初始值设定项,那么您也可以使用 @ViewBuilder
作为参数。即使对于多个参数,如果您愿意:
init(@ViewBuilder content: () -> Content) {…}
接受的答案很简单。 iOS 14 + macOS 11:
语法变得更清晰
struct ContainerView<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
content
}
}
然后继续这样使用:
ContainerView{
...
}
或者,您可以使用静态函数扩展。例如,我对 Text 做了一个 titleBar 扩展。这使得重用代码变得非常容易。
在这种情况下,您可以传递一个带有视图闭包的@Viewbuilder 包装器,返回一个符合视图的自定义类型。例如:
import SwiftUI
extension Text{
static func titleBar<Content:View>(
titleString:String,
@ViewBuilder customIcon: ()-> Content
)->some View {
HStack{
customIcon()
Spacer()
Text(titleString)
.font(.title)
Spacer()
}
}
}
struct Text_Title_swift_Previews: PreviewProvider {
static var previews: some View {
Text.titleBar(titleString: "title",customIcon: {
Image(systemName: "arrowshape.turn.up.backward")
})
.previewLayout(.sizeThatFits)
}
}
我正在实施一个名为 MenuItem
非常 的自定义 NavigationLink,并希望在整个项目中重复使用它。它是一个符合 View
并实现 var body : some View
的结构,其中包含一个 NavigationLink
。
我需要以某种方式将 NavigationLink
应呈现的视图存储在 MenuItem
的正文中,但尚未这样做。
我在 MenuItem
的正文中将 destinationView
定义为 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...
}
}
-->错误:协议'View'只能用作泛型约束,因为它有Self或关联类型要求。
第二次尝试:
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...
}
}
--> 错误: 无法将类型 '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...
}
}
--> 错误: 无法将类型 'View' 的值分配给类型 'some View'.
我希望有人能帮助我。如果 NavigationLink 可以接受一些 View 作为参数,则必须有一种方法。 谢谢 ;D
您应该将通用参数设为 MenuItem
的一部分:
struct MenuItem<Content: View>: View {
private var destinationView: Content
init(destinationView: Content) {
self.destinationView = destinationView
}
var body : some View {
// ...
}
}
Apple 的做法是使用函数生成器。有一个预定义的 ViewBuilder
。将它设为 MenuItem
的 init
方法的最后一个参数,或唯一的参数,如下所示:
..., @ViewBuilder builder: @escaping () -> Content)
将其分配给 属性 定义如下:
let viewBuilder: () -> Content
然后,在您想显示 passed-in 视图的地方,只需像这样调用函数:
HStack {
viewBuilder()
}
您将能够像这样使用您的新视图:
MenuItem {
Image("myImage")
Text("My Text")
}
这将允许您传递最多 10 个视图并使用 if
条件等。但如果您希望它更具限制性,则必须定义您自己的函数构建器。我还没有这样做,所以你将不得不 google 那。
您可以像这样创建自定义视图:
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()
}
}
您可以将 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")
}
}
为了 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()
}
}
}
}
}
总结一下我在这里阅读的所有内容以及对我有用的解决方案:
struct ContainerView<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
content
}
}
这不仅允许您在里面放置简单的 View
s,而且,感谢 @ViewBuilder
,使用 if-else
和 switch-case
块:
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")
}
}
}
}
奖金: 如果你想要一个贪婪的容器,它将要求所有可能的 space (与上面的容器相反,它只要求其子视图所需的 space )这里是:
struct GreedyContainerView<Content: View>: View {
@ViewBuilder let content: Content
var body: some View {
content
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
如果您需要在视图中使用初始值设定项,那么您也可以使用 @ViewBuilder
作为参数。即使对于多个参数,如果您愿意:
init(@ViewBuilder content: () -> Content) {…}
接受的答案很简单。 iOS 14 + macOS 11:
语法变得更清晰struct ContainerView<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
content
}
}
然后继续这样使用:
ContainerView{
...
}
或者,您可以使用静态函数扩展。例如,我对 Text 做了一个 titleBar 扩展。这使得重用代码变得非常容易。
在这种情况下,您可以传递一个带有视图闭包的@Viewbuilder 包装器,返回一个符合视图的自定义类型。例如:
import SwiftUI
extension Text{
static func titleBar<Content:View>(
titleString:String,
@ViewBuilder customIcon: ()-> Content
)->some View {
HStack{
customIcon()
Spacer()
Text(titleString)
.font(.title)
Spacer()
}
}
}
struct Text_Title_swift_Previews: PreviewProvider {
static var previews: some View {
Text.titleBar(titleString: "title",customIcon: {
Image(systemName: "arrowshape.turn.up.backward")
})
.previewLayout(.sizeThatFits)
}
}