skip to Main Content

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 // ERROR Happens here.
}

Cannot assign value of type ‘V’ to type ‘some MyProtocol’.

Why do I get this error and how can it be avoided?

2

Answers


  1. 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.)

    That "secret" type is carried along, however, and is well defined, it’s just opaque. For example, the following is fine:

    var contentView: some MyProtocol = MyView()
    let otherView = contentView // otherView is definitely the same as as contentView
    contentView = otherView     // so it can be assigned
    

    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.

    For more details of opaque types, see SE-244.

    Login or Signup to reply.
  2. 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 ViewControllers that may be.

    extension ViewController where View == MyView {
      convenience init() {
        self.init(contentView: .init())
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search