Search code examples
iosswiftgenericsswift-playground

Swift/iOS: Generics Error: protocol requires nested type 'V'; do you want to add it?


I am using Playground, Swift version 5.7.1.

I have two protocols, and two classes. This first one is simple and it works:

protocol TestSomeB: ObservableObject {
   associatedtype V: View
   func returnSome() -> V
}

class TestSomeBImp: TestSomeB {
   init() {}

   func returnSome() -> some View {
      Text("Hello World")
   }
}

let testSomeBImp = TestSomeBImp()
testSomeBImp.returnSome()

This works and gives me the value {{SwiftUI.AnyTextStorage, {key "Hello World", hasFormatting false, []}, nil, nil}}

The second one doesn't work even though the basic code structure is the same:

struct TestModel {
   var title: String
}

struct TestView: View {
   var body: some View {
       Text("Hello, World!")
   }
}

// similar to protocol TestSomeB
protocol TestSomeA: ObservableObject {
    associatedtype V: View
    func linkBuilder<Content: View>(data: TestModel, @ViewBuilder content: () -> Content) -> V
}

class TestSomeAImp: TestSomeA {
   init() {}

   // returns `some View` similar to returnSome() method above
   func linkBuilder<Content: View>(data: TestModel, @ViewBuilder content: () -> Content) -> some View {
      NavigationLink(destination: routeToPage(data: data)) {
         content()
      }
   }

   private func routeToPage(data: TestModel) -> some View {
       TestView()
   }
}

let testSomeImp = TestSomeAImp()
testSomeImp.linkBuilder(
    data: TestModel(title: "Hello "),
    content: {
       Text("World!")
    }
)

Sadly this gives me the error: protocol requires nested type 'V'; do you want to add it? associatedtype V: View

  • I need to return some View but I also need to abstract my implementation.
  • I tried using Content instead of V in the return type but that gives me error too.
  • I tried using only associatedtype V in the protocol without specifying the type, but that also gives me an error.
  • I tried creating two associated types, one V, and the other for Content, but that actually gave me the same nested error
  • I tried adding the typealias V, but since it's nested, the error keeps occurring again and again.

Please advise. Thanks!


Solution

  • Your protocol requirement is that you offer this:

    func linkBuilder<Content: View>(data: TestModel, @ViewBuilder content: () -> Content) -> V
    

    Given a Content, you promise to always return a value of type V, which is determined by the implementation of the protocol. There is precisely one type V for a given implementation type.

    You then you implement it as:

    func linkBuilder<Content: View>(data: TestModel, @ViewBuilder content: () -> Content) -> some View {
    

    This says that linkBuilder will return an opaque type that depends on Content, which is determined by the caller. Each call to linkBuilder can return a different specific type, depending on what Content is. This does not match "will always return V, which is a specific, concrete type defined at compile time for this implementation."

    In your first example, the type returned is exactly Text. In your failing example, your type depends on Content.

    Try getting rid of all of the some types, and it will be more clear what's going on. (A some type must always be replaceable with a known-at-compiletime, type. It's opaque; it's not dynamic.)

    func linkBuilder<Content: View>(data: TestModel, @ViewBuilder content: () -> Content)
        -> NavigationLink<Content, TestView> {
        NavigationLink(destination: routeToPage(data: data), label: content)
    }
    
    private func routeToPage(data: TestModel) -> TestView {
        TestView()
    }
    

    So you could make a protocol for that certainly, though I expect this isn't what you want:

    protocol TestSomeA: ObservableObject {
        func linkBuilder<Content: View>(data: TestModel, 
                                        @ViewBuilder content: () -> Content) 
            -> NavigationLink<Content, TestView>
    }
    

    Another important thing to understand here is that NavigationLink<Text, TestView> is a completely different type than NavigationLink<Image, TestViw>. There is no way to directly express "a NavigationLink with arbitrary parameters."

    It's not clear here what the point of the protocol is here. It's not clear what another implementation of TestSomeA would look like, and how callers would use it without knowing the types. Your linkBuilder is fine; it's just the throwing in a protocol that makes it messy. What problem is this solving?