Cannot convert return expression of type 'Text' to return type 'some View'

Hey,
I have created this protocol to be able to use some View instead of AnyView but when I conform a class to this view and try to implement it, I get an error.

public protocol LaunchPadRouterProtocol: AnyObject {
  associatedtype body: View
   func launchPad() -> body
   func openReel(lessonSelectorOutput: LessonSelectorOutput) -> body
}

final class LaunchPadRouter: ObservableObject, LaunchPadRouterProtocol {
  func launchPad() -> some View {
    Text("")
  }
  
  func openReel(lessonSelectorOutput: LessonSelectorOutput) -> body {
    Text(“”)  // Error: Cannot convert return expression of type 'Text' to return type 'some View'
  }
}

And when I change it to this, it also gives me an error :

public protocol LaunchPadRouterProtocol: AnyObject {
  associatedtype body: View
   func launchPad() -> body
   func openReel(lessonSelectorOutput: LessonSelectorOutput) -> body
}

final class LaunchPadRouter: ObservableObject, LaunchPadRouterProtocol {  // Error: Type 'LaunchPadRouter' does not conform to protocol 'LaunchPadRouterProtocol'
  func launchPad() -> some View {
    Text("")
  }
  
  func openReel(lessonSelectorOutput: LessonSelectorOutput) -> some View {
    Text(“”)
  }
}

but when I make the protocol consists of one function it works like this:

public protocol LaunchPadRouterProtocol: AnyObject {
  associatedtype body: View
   func launchPad() -> body
}

final class LaunchPadRouter: ObservableObject, LaunchPadRouterProtocol {  
  func launchPad() -> some View {
    Text("")
  }
  
}

My goal is to use some View instead of AnyView

Hello,

LaunchPadRouterProtocol, as written, requires both launchPad and openReel to return the exact same type of View.

Unfortunately, LaunchPadRouter does not provide that guarantee. As far as the compiler is concerned, its launchPad and openReel methods return two distinct types of views, and the compiler won't accept that they're identical.

You need to instruct the compiler that both methods do return the same type.

You can do this as below, but this is not very ergonomic. You have to exactly specify the full type - easy for Text, but less easy for more complex SwiftUI views:

final class LaunchPadRouter {
    typealias Body = Text
    func launchPad() -> Body { Text("") }
    func openReel() -> Body { Text("") }
}

Another technique is to encode the shared view type in a helper type. This helper type gives a name to the opaque type, and makes it possible to fullfill the protocol requirement. This is a technique that is often useful in complicated generic setups. Now you can keep the body type opaque with some View:

final class LaunchPadRouter {
    struct Content: View {
        var body: some View {
            Text("")
        }
    }

    func launchPad() -> Content { Content() }
    func openReel() -> Content { Content() }
}

Finally, I would question the need that both methods have the same type. I'm not sure it is desirable, because as seen above, it is not an easy constraint to fulfill. And I'm not sure it is necessary, because we rarely depend on the actual type of SwiftUI views. What can turn wrong if both methods do not return the same type, as below?

public protocol LaunchPadRouterProtocol: AnyObject {
    associatedtype LaunchPadBody: View
    associatedtype ReelBody: View
    func launchPad() -> LaunchPadBody
    func openReel() -> ReelBody
}

Hope this helps!

3 Likes

Thank you for this solution:

public protocol LaunchPadRouterProtocol: AnyObject {
    associatedtype LaunchPadBody: View
    associatedtype ReelBody: View
    func launchPad() -> LaunchPadBody
    func openReel() -> ReelBody
}

But it’s very weird because if I made both return some View it says they are diffrent, like why they are diffrent both of them are of oblique type some View

Indeed the two methods below both return some View, but they do not have the same types at all. One is Text, one is Button. It is not because both are spelled some View that they are identical:

func text() -> some View { Text("") }
func button() -> some View { Button("") { } }

See Opaque and Boxed Types in the Swift Programming Language for more information.

2 Likes

Yeah, I got it. first I thought that Text conforms to View. but even when I write Vstack which conforms to View it gives me an error. why is that. if both of them returns the same type View

One More thing now I get this error “Type 'LaunchPadRouter.ReelRouterView' (aka 'some View') has no member 'init’"

public protocol LaunchPadRouterProtocol: AnyObject {
  associatedtype ReelRouterView: View
  
  @ViewBuilder func openReel(lessonSelectorOutput: LessonSelectorOutput) -> ReelRouterView
}

final class LaunchPadRouter: ObservableObject, LaunchPadRouterProtocol {
  func openReel(lessonSelectorOutput: LessonSelectorOutput) -> some View {
    ReelRouterView(lessonSelectorOutput: lessonSelectorOutput, isRestartPressed: true) // Error: Type 'LaunchPadRouter.ReelRouterView' (aka 'some View') has no member 'init'
  }
}

struct ReelRouterView: View {
  @StateObject var router: ReelRouter = .init()
  var lessonSelectorOutput: LessonSelectorOutput = ([], [], false)
  let isRestartPressed: Bool

  var body: some View {
    self.router.startPlayer(isRestartPressed: isRestartPressed, lessonSelectorOutput: lessonSelectorOutput)
  }
}

@gwendal.roue

You have a name conflict here: the protocol associated type and view implementation is conflicting. And Swift uses “closest” namespace which is protocol one, which has information only about View protocol, and it has no initializers. You need to use a different name for one of them, e.g. name concrete implementation ReelView.

1 Like