我正在实现一个名为 MenuItem
的
very自定义 NavigationLink,并希望在整个项目中重用它。它是一个符合
View
并实现 var body : some View
的结构体,其中包含 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...
}
}
--> 错误: 协议“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”类型的值分配给“某个视图”类型。
最后的尝试:
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
总结我在这里读到的所有内容以及对我有用的解决方案:
struct ContainerView<Content: View>: View {
@ViewBuilder let content: Content
var body: some View {
content
}
}
这不仅允许您将简单的
View
放入其中,而且由于 @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")
}
}
}
}
奖金: 如果你想要一个贪婪的容器,它将声明所有可能的空间(与上面的容器相反,它只声明其子视图所需的空间),它是:
struct GreedyContainerView<Content: View>: View {
@ViewBuilder let content: Content
var body: some View {
content
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
如果您的视图中需要初始化程序,那么您也可以使用
@ViewBuilder
作为参数。即使对于多个参数,如果您愿意:
init(@ViewBuilder content: () -> Content) {…}
Apple 的做法是使用函数构建器。有一个预定义的称为
ViewBuilder
。使其成为 init
的 MenuItem
方法的最后一个参数或唯一的参数,如下所示:
..., @ViewBuilder builder: @escaping () -> Content)
将其分配给定义如下的属性:
let viewBuilder: () -> Content
然后,在您想要显示传入视图的地方,只需调用如下函数即可:
HStack {
viewBuilder()
}
您将能够像这样使用新视图:
MenuItem {
Image("myImage")
Text("My Text")
}
这将允许您传递最多 10 个视图并使用
if
条件等。但如果您希望它更具限制性,则必须定义自己的函数构建器。我还没有这样做,所以你必须谷歌一下。
您应该将通用参数作为
MenuItem
的一部分:
struct MenuItem<Content: View>: View {
private var destinationView: Content
init(destinationView: Content) {
self.destinationView = destinationView
}
var body : some View {
// ...
}
}
您可以像这样创建自定义视图:
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")
}
}
接受的答案既好又简单。 iOS 14 + macOS 11,语法变得更加清晰:
struct ContainerView<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
content
}
}
然后继续这样使用:
ContainerView{
...
}
我真的很难让我的工作延长
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()
}
}
}
}
}
您也可以使用静态函数扩展。例如,我将 titleBar 扩展为 Text。这使得重用代码变得非常容易。
在这种情况下,您可以传递一个 @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)
}
}
如果有人试图将两个不同的视图传递给其他视图,并且由于此错误而无法做到这一点:
无法生成表达诊断;请提交错误报告...
因为我们正在使用
解决方案很简单,添加另一个内容视图,并以不同的方式命名。
示例:
struct Collapsible<Title: View, Content: View>: View {
@State var title: () -> Title
@State var content: () -> Content
@State private var collapsed: Bool = true
var body: some View {
VStack {
Button(
action: { self.collapsed.toggle() },
label: {
HStack {
self.title()
Spacer()
Image(systemName: self.collapsed ? "chevron.down" : "chevron.up")
}
.padding(.bottom, 1)
.background(Color.white.opacity(0.01))
}
)
.buttonStyle(PlainButtonStyle())
VStack {
self.content()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: collapsed ? 0 : .none)
.clipped()
.animation(.easeOut)
.transition(.slide)
}
}
}
调用此视图:
Collapsible {
Text("Collapsible")
} content: {
ForEach(1..<5) { index in
Text("\(index) test")
}
}
2 个视图的语法
struct PopOver<Content, PopView> : View where Content: View, PopView: View {
var isShowing: Bool
@ViewBuilder var content: () -> Content
@ViewBuilder var popover: () -> PopView
var body: some View {
ZStack(alignment: .center) {
self
.content()
.disabled(isShowing)
.blur(radius: isShowing ? 3 : 0)
ZStack {
self.popover()
}
.frame(width: 112, height: 112)
.opacity(isShowing ? 1 : 0)
.disabled(!isShowing)
}
}
}