Can a protocol return a (SwiftUI) View? - Type 'any View' cannot conform to 'View'

I have various items which get shown in a grid view.

For some of them, I'd like to delegate to the items to provide a detail overlay

Items optionally conform to ProvidesGridDetails

protocol ProvidesGridDetails {
    associatedtype DetailGridView: View
    var gridCustomDetails: DetailGridView {get}
}

my view would then check protocol conformance and call the item if appropriate

    var body: some View {
        if let customGridItem = item as? ProvidesGridDetails {
            customGridItem.gridCustomDetails
        }
        else {
            //fallback
        }
    }

this gives me the error Type 'any View' cannot conform to 'View' (swift 5.7)

is there a way to achieve this?

2 Likes

Will AnyView work for you?

protocol ProvidesGridDetails {
	var gridCustomDetails: AnyView { get }
}
2 Likes

What is the static type of item?

What is the static type of item ?

unspecified. It could be one of various types conforming to the protocol.

I'm not sure what you mean. Can you show the code declaring item?

AnyView does the job.

I'm conditioned to think that's "a bad thing ™️" though. Perhaps there isn't a better option...

It certainly seems nuts for the error to say 'any View' cannot conform to 'View'!
Surely that's the one thing we do know about it!

I'm not sure what you mean. Can you show the code declaring item ?

struct Foo: ProvidesGridDetails  {
  var gridCustomDetails: DetailGridView {
    Text("I'm a Foo")
  }
}

struct Bar: : ProvidesGridDetails  {
//different implementation
  var gridCustomDetails: DetailGridView {
    Capsule().fill(.green)
  }
}

struct Other  {
//note this doesn't conform to ProvidesGridDetails, so we won't call gridCustomDetails
}

I still don't understand, as I'm not seeing anything declared item in this code, but I'm glad you're able to use AnyView to solve your problem.

I am no expert, but my understanding is as follows (please verify with others):

I think the error needs to be read as Type 'any View' cannot conform to some 'View'

some View is an opaque type

In your if let statement which ever execution path the program takes the returned view needs to be of the same type (for example they returned Text("aaa") and Text("bbb"), it would compile fine)

So casting it to AnyView solves the problem, that way both execution path return AnyView and satisfies the opaque type requirement

1 Like

There's also some other v̵̡̱̩̠͔̮̖̒o̸͙͉̙̳̙̗̱͔̹͎̤̾̔̈́͜o̷̡̧̢̨̻̰͓̖̥̙̭̲͇̫͈͛̓̈̈́̕d̶̘̳͙̠͉̯̣̰̥͍̭̫̯͊̈͜o̴̢̧̡͖͉̬͖̻̼͚͈̓̍́̉̕͝ȍ̴̹̼̖̞͙̫̬͖̋̅̔̋ ̵͇̋͗͝m̵̛̱̬̠͍͖̹̼̩͉͕͖̲̓͂̈́̈́̾͊̌̈̊̌̄͘͝à̷̢̧̛̺͙̪̤͈͊̐̉̏͊͗̓̑̎̎̑͌̀ģ̴̡̛͕͕͖̯͈̪̭͇͇̟̄̉̔̾̌͆̓̀̍̔̋̀i̸̛̯͖͖̐̅̈̀̏̒̈̒̏͊̀c̸̨̧̛̣̫̗̲͇̻̖̱̺̹̬̏̃͊̅͂̒̎̎͜͜͝ related to body:

var body: some View {
    if param {
        Text("Hello")
    } else {
        Color.red
    }
}

// Error below: Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
var body2: some View {
    if param {
        return Text("Hello") // ditto without "return"
    } else {
        return Color.red
    }
}

Yep, @ConfusedVorlon it would make sense to provide a more complete code. Another way to solve this would be to make the view generic, it depends upon what exactly your "item" is.

here's the whole view:

struct GridItemCentreTitleInfoOverlay: View {

    let item: GridDisplayable

    var body: some View {

        ZStack(alignment: .center) {

            VStack(alignment: .leading) {
                Spacer()
                    .frame(maxWidth: .infinity)

                if let meditation = item as? GridDetails {
                    meditation.details
                } else {
                    ForEach(item.gridDetails, id: \.self) {
                        detail in
                        ByView(detail: detail)
                            .specialFont(.footnote)
                            .foregroundColor(Color.secondary)
                            .shadow(color: .shadow, radius: 1, x: 1, y: 1)
                    }
                }

            }
            .padding(10)

            Text(item.gridTitle)
                .specialFont(.title)
                .shadow(color: .shadow, radius: 5, x: -2, y: 2)
                .padding(10)

        }
        .lineLimit(1)
        .minimumScaleFactor(0.5)
        .foregroundColor(.white)

    }

}

GridDisplayable is a protocol which requires that the item provides e.g. a title and a background image so it can be shown in a grid view

some displayed items also conform to GridDetails, and that's where I delegate the details to the item.

Some essential things are still missing from this code. GridDisplayable, GridDetails, ByView not shown. OTOH many details there could be removed (fonts / shadows, or irrelevant to this question ZStack, VStack, etc).

To see it how we see it - create a brand new project and copy paste your code into it - it will show all things missing.

PS. you can edit your past messages.

GridDisplayable is a protocol. It really doesn't matter what is in it

ByView is just a view - it really doesn't matter what it does.

protocol GridDetails {
	var details: AnyView { get }
}

this isn't a question about rendering a specific view.

It's a question about whether you can check protocol conformance on an item - and call a method returning a view if it conforms.

that's now working with AnyView

This isn't that magic, it just isn't obvious: body inherits the @ViewBuilder attribute from the protocol requirement, meaning it's a builder. Your separate property does not. Simply marking the other property @ViewBuilder will give it the same behavior.

1 Like

I think you can keep your ProvidesGridDetails unchanged and wrap it with AnyView instead like:

HStack {
    AnyView(ConcretProvidesGridDetails().gridCustomDetails)
}

Anyone else find a solution here?

protocol CustomizationPoint {
    associatedtype Content: View
    var customView: Content { get }
}

struct NoNetworkView: View {
    let customization: any CustomizationPoint
    
    public var body: some View {
        Text("Hello World")
        customization.customView  // <-- Type 'any View' cannot conform to 'View'
    }
}

We can wrap customization.customView in AnyView(), but this has some performance implications. Do any of the new Swift generics and existential implicit opening stuff allow us to avoid AnyView here?

1 Like

I'd make the NoNetworkView generic:

protocol CustomizationPoint {
    associatedtype Content: View
    var customView: Content { get }
}

struct NoNetworkView<Content: CustomizationPoint>: View {
    let customization: Content
    
    public var body: some View {
        Text("Hello World")
        customization.customView  // <-- Now this compiles
    }
}

Thanks @bjhomer! That works, though when I try it with the actual setup I have, it does not:

import Foundation
import SwiftUI

protocol CustomizationPoint<T> {
  associatedtype T
  var customization: T? { get }
}

protocol ContentViewCustomizing {
  associatedtype Content: View
  var customView: Content { get }
}

struct NoNetworkViewCustomization: CustomizationPoint {
  typealias T = ContentViewCustomizing
  var customization: (any T)? {
      // return some customization
      nil
  }
}


struct NoNetworkView<Content: ContentViewCustomizing>: View {
    public var body: some View {
        Text("Hello World")
        attachCustomView()
    }

    @ViewBuilder
    private func attachCustomView() -> some View {
        if let customization = NoNetworkViewCustomization().customization {
            customization.customView  // Error: Type 'any View' cannot conform to 'View'
        }
    }
}

Any way to make this work? Thank you!