Why does `ViewModifier` protocol has an `associatedtype` AND a `typealias`?

As far as I know, the definition of the ViewModifier protocol looks like this:

protocol ViewModifier {

    // content view type passed to body()
    typealias Content

    // type of view returned by body()
    associatedtype Body : View

    // only requirement
   func body(content: Self.Content) -> Self.Body

}

My question is:

Why Self.Content is a typealias while Self.Body is an associatedtype ? What's the difference?

2 answers

  • answered 2020-09-10 13:48 pawello2222

    • typealias only changes the name of the type. Nothing more.

    • associatedtype is a way to include generics in the protocol implementation.


    public protocol ViewModifier {
    
        /// The type of view representing the body of `Self`.
        associatedtype Body : View
    
        /// Returns the current body of `self`. `content` is a proxy for
        /// the view that will have the modifier represented by `Self`
        /// applied to it.
        func body(content: Self.Content) -> Self.Body
    
        /// The content view type passed to `body()`.
        typealias Content
    }
    

    In the above example Body is a concrete type conforming to View and is inferred by the body(content:) return type.

    Content is just another name for a type passed as the content parameter.


    This answer by rob mayoff explained the ViewModifier in a more detailed way:

    So this tells us that, when we write our own ViewModifier, our body method will receive some sort of View (the specific type is defined by the framework and we can just call it Content), and return some sort of View (we get to pick the specific return type).

    This means you don't know what the Content is, you just operate on it - it's called Content for your convenience, so you don't have to deal with _ViewModifier_Content<Self>.

  • answered 2020-09-10 20:23 rob mayoff

    Why Self.Content is a typealias while Self.Body is an associatedtype ? What's the difference?

    Because Content is a typealias, the author of the ViewModifier protocol gets to pick the type being aliased when she writes the protocol. (You can't see the type being aliased because that type is _ViewModifier_Content<Self>. When an identifier in the SDK starts with _, Apple omits the identifier from documentation and generated interfaces.)

    Because Body is an associatedtype, you get to pick the type that it aliases, when you write a type that conforms to the ViewModifier protocol. You can make Body be any type you want, subject to two conditions:

    • You must pick a type that conforms to View (because the ViewModifier protocol constrains Body to conform to View).

    • You must be able to create or obtain an instance of whatever type you pick, because you have to return an instance of it from the body method. (Or you could crash or hang to avoid returning at all, but that's usually not what you want…)

    So, when you implement a type conforming to ViewModifier, you cannot influence what Content means. It always means _ViewModifier_Content<Self>. But you can choose what Body means, by choosing the return type of the body method.

    Here's I'll force Body to mean EmptyView:

    struct EmptyModifier: ViewModifier {
        func body(content: Content) -> EmptyView {
            EmptyView()
        }
    }
    

    And here I'll force Body to mean Color:

    struct RedModifier: ViewModifier {
        func body(content: Content) -> Color {
            Color.red
        }
    }
    

    Usually we use some View as the type, which means that Swift deduces the exact type for us and keeps it a secret:

    struct FrameModifier: ViewModifier {
        var color: Color
        var width: CGFloat
    
        func body(content: Content) -> some View {
            return content
                .padding(width)
                .border(color, width: width)
        }
    }
    

    Here, all we know about the Body type is that it conforms to View. Swift tries pretty hard to keep us from finding out the real type at compile time.