简体   繁体   English

使用 SwiftUI 中的协议来提供“一些视图”/Generics?

[英]Using protocol in SwiftUI for providing "some View" / Generics?

I'm trying to get my head something in SwiftUI.我正试图在 SwiftUI 中得到一些东西。 I want to build a SwiftUI view and have something you could call a ViewProvider as a @State var .我想构建一个 SwiftUI 视图,并有一些你可以将ViewProvider称为@State var的东西。 something like this:像这样的东西:

protocol ViewProvider {
    associatedtype ViewOne = View
    associatedtype ViewTwo = View
    
    @ViewBuilder var viewOne: ViewOne { get }
    @ViewBuilder var viewTwo: ViewTwo { get }
}    

struct ContentView: View {
    @State private var parent: ViewProvider?
    
    var body: some View {
        VStack {
            HStack {
                Button(action: { parent = Father() }, label: { Text("Father") })
                Button(action: { parent = Mother() }, label: { Text("Mother") })
            }
            
            if let p = parent {
                p.viewOne
                p.viewTwo
            }
        }
    }
}

class Father: ViewProvider {
    @ViewBuilder var viewOne: some View {
        Text("Father One!")
    }
    
    @ViewBuilder var viewTwo: some View {
        Text("Father Two!")
    }
}


class Mother: ViewProvider {
    @ViewBuilder var viewOne: some View {
        Text("Mother One!")
    }
    
    @ViewBuilder var viewTwo: some View {
        Text("Mother Two!")
    }
}

This produces 2 different compiler errors.这会产生 2 个不同的编译器错误。

@State private var parent: ViewProvider?
// Protocol 'ViewProvider' can only be used as a generic constraint because it has Self or associated type requirements

and

p.viewOne
p.viewTwo
// 2x Member 'viewOne' cannot be used on value of protocol type 'ViewProvider'; use a generic constraint instead

I have a vague idea of what I'm doing wrong, but no idea on how to solve it:) What syntax should I use to get something like this to work?我对自己做错了什么有一个模糊的想法,但不知道如何解决它:) 我应该使用什么语法来使这样的事情起作用?

Assuming you're on Swift 5.6 or lower, the problem is that you can only use protocols with associated types for conformance, ie you can't use them as types to pass around.假设您使用的是 Swift 5.6 或更低版本,问题是您只能使用具有关联类型的协议以实现一致性,即您不能将它们用作传递的类型。 The reasoning is that their associated types will be different for different conformers.原因是它们的关联类型对于不同的conformers会有所不同。

Say you have the following:假设您有以下内容:

protocol P {
    associatedtype T
    var prop: T 
}

class MyClass: P {
    var prop: Int 
}

class MyOtherClass: P { 
    var prop: String
}

What would the result of the following be?下面的结果会是什么?

let arr: [P] = [MyClass(), MyOtherClass()]
let myMappedArr = arr.map { $0.prop }

prop is of a different type for each conformer. prop对于每个conformer都是不同的类型。


In Swift 5.7, however, you actually can pass around protocols of this sort.然而,在 Swift 5.7 中,您实际上可以传递此类协议。 In later versions of Swift, you will have to use the keyword any to pass these protocols around as types.在 Swift 的更高版本中,您必须使用关键字any将这些协议作为类型传递。

See the proposal for unlocked existentials to learn more about it.请参阅解锁存在的提案以了解更多信息。


Lastly to address opaque types here:最后在这里解决不透明类型:

Since you can't pass around protocols with associated types, you can't have something like由于您不能传递具有关联类型的协议,因此您不能拥有类似的东西

@State var myState: ViewProvider or even @State var myState: some ViewProvider , because your state variable is assigned , and you can't assign something of an opaque type. @State var myState: ViewProvider甚至@State var myState: some ViewProvider ,因为您的 state 变量已分配,并且您不能分配不透明类型的东西。

In SwiftUI's View, this works because the view property is computed, and thus the type can be inferred在 SwiftUI 的 View 中,这是可行的,因为view属性是计算出来的,因此可以推断出类型

// type is inferred to be (something like) Group<Text>
var body: some View {
    Group {
       Text("something")
    }
} 

whereas here, you can't find a suitable type to assign to a property whose type is opaque而在这里,您找不到合适的类型来分配给类型不透明的属性

@State var myState: some ViewProvider
...

// You don't know myState's type, so you can't assign anything to it
myState = ... // error - you won't be able to find a matching type to assign to this property

To wit, the line @State private var parent: ViewProvider?也就是说, @State private var parent: ViewProvider? in your code simply won't compile in Swift 5.6 or lower, because you're not allowed to use your ViewProvider protocol as a type for anything other than conformance or as an opaque return type when used in functions or computed properties.在您的代码中根本不会在 Swift 5.6 或更低版本中编译,因为在函数或计算属性中使用时,不允许将 ViewProvider 协议用作除一致性之外的任何类型或不透明返回类型。


Sorry for all the edits.很抱歉所有的编辑。 Wanted to provide a couple of potential solutions:希望提供几个潜在的解决方案:

One way is to simply make your ContentView generic over the type of its ViewProvider一种方法是简单地使您的 ContentView 在其 ViewProvider 的类型上通用

struct ContentView<ViewProviderType: ViewProvider> {
     @State private var parent: ViewProviderType?
     ... 
}

The other would be to simply remove the associatedtype from your protocol and just erase the view type you're trying to return:另一种是简单地从您的协议中删除 associatedtype 并删除您尝试返回的视图类型:

protocol ViewProvider {
     var viewOne: AnyView { get }
     var viewTwo: AnyView { get }
}

If you're working with Swift 5.7, you may be able to use your type-constrained protocols as property types, or you can also use primary associated types , wherein you could declare properties of type ViewProvider<MyView> (though that doesn't necessarily solve your problem).如果您正在使用 Swift 5.7,您可以将类型约束协议用作属性类型,或者您也可以使用主要关联类型,其中您可以声明ViewProvider<MyView>类型的属性(尽管这不是一定能解决你的问题)。

Generics or type erasure over ViewProvider's view types are probably the best candidates for what you're trying to do, even in a Swift 5.7 world.即使在 Swift 5.7 世界中,Generics 或 ViewProvider 的视图类型上的类型擦除可能是您尝试做的最佳选择。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM