I have a protocol:
import SwiftUI
...
protocol MyProtocol : View
{
var aValue: CGFloat { get }
}
Then I have a property in a UIViewController
:
var contentView: some MyProtocol = MyView()
Where MyView
is:
struct MyView : MyProtocol
{
var aValue: CGFloat = 0.25
var body: some View
{
...
}
}
Back in my view controller I have:
func showView<V: MyProtocol>(view: V)
{
...
contentView = view <== Compiler error
}
and get the following error: Cannot assign value of type 'V' to type 'some MyProtocol'
.
Why do I get this error and how can it be avoided?
CodePudding user response:
var contentView: some MyProtocol = MyView()
So the type of contentView
is "some specific, secret (opaque) type, unknown to anything but the compiler, that conforms to MyProtocol, and also happens to be exactly MyView, even though nothing can know that." It's not "something that conforms to MyProtocol" which it seems maybe you're thinking it is. If you mean that, the syntax is:
var contentView: MyProtocol = MyView()
The point of some
is that the type is statically known at compile-time by the compiler, but not known to the caller, or by anything else.
For example, even this would fail:
var contentView: some MyProtocol = MyView()
contentView = MyView() // Cannot assign value of type 'MyView' to type 'some MyProtocol'
The compiler will not prove that MyView is exactly the secret type that contentView
used. (For most errors of this type I'd say the compiler "cannot prove," but in this case, it's an active decision to forbid proving the fact because that's what some
does.)
At first pass, I expect the code you want is just the above var contentView: MyProtocol
, but it's very possible you have a deeper misunderstanding about SwiftUI. You cannot just swap in arbitrary Views in SwiftUI. As a rule, everything should be decided at compile-time, not runtime. There are tools like AnyView to work around this, but generally should not be your first choice. I expect there's a deeper design problem here that isn't in your question.
CodePudding user response:
See Rob's answer for a good explanation of why, currently, your view controller is generic as follows and you haven't realized it.
final class ViewController<View: MyProtocol> {
private(set) var contentView: View
init(contentView: View) {
self.contentView = contentView
}
func showView(view: View) {
contentView = view
}
}
The property initializer you're using only applies to one of the potentially infinite ViewController
s that may be.
extension ViewController where View == MyView {
convenience init() {
self.init(contentView: .init())
}
}