Ale*_*lex 7 generics protocols view swift swiftui
我正在实现一个非常自定义的NavigationLink MenuItem,它想在整个项目中重复使用。这是一个符合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...
}
}
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-else和switch-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)
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)
您应该将通用参数作为以下内容的一部分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)
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个视图,并在条件等条件下使用。尽管如果您希望限制更多,则必须定义自己的函数生成器。我还没有这样做,所以您将不得不用谷歌搜索。
我真的很努力地让我的工作延伸到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)
您可以这样创建自定义视图:
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)
您可以将 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)