Astro
Astro

Reputation: 229

How to display protocol view in a SwiftUI View?

I have a protocol for an object as such:

public protocol Viewable {
    associatedtype Content: View

    var parent: (any Viewable)? { get }
    var body: Content { get }
}

What I would like is to show the body of the view inside of another view, something like this:

public struct SampleView: View {
    
    var object: (any Viewable)?
    
    public var body: some View {
        object!.body 
    }
}

But I am getting an error Type 'any View' cannot conform to 'View'

What would be the best way to show a view of an object which was defined in a protocol extension without using AnyView?

Thanks.

Upvotes: 2

Views: 816

Answers (1)

Bradley Mackey
Bradley Mackey

Reputation: 7668

Your object cannot be an existential type (a dynamically typed protocol) because the protocol requirements for View specify the View.Body is a generic, associated type.

We need to retain the full, generic type information in order to satisfy the some View requirement for body on View. The answer is to use a concrete generic type:

public struct SampleView: View {
    
    var object: (some Viewable)?
    
    public var body: some View {
        object!.body 
    }
}

For clarity, this can be spelled more traditionally to make it clear this is a generically constrained type to your custom protocol:

public struct SampleView<T: Viewable>: View {
    
    var object: T?
    
    public var body: some View {
        object!.body
    }
}

This behaviour is detailed more in the proposal for SE-0309 which originally allowed existential types to be used for any protocol type.


To still use Viewable types heterogeneously (such as in an Array), you'll need to create an additional layer of abstraction, such as creating an enum with concrete values as associated values.

struct SampleView: Viewable { ... }
struct ProfileView: Viewable { ... }
struct PreferencesView: Viewable { ... }

enum Views {
   case sample(SampleView)
   case profile(ProfileView)
   case preferences(PreferencesView)
}

var views = [Views]()

Now you can access the concrete types in the associated values. Obviously this solution does not scale as well though, and this kind of approach to dealing with generic types should be used in limited circumstances to avoid this enum becoming very large with many cases.

Upvotes: 2

Related Questions