swiftstructenumsstatic

How does Apple's framework uses structs with static properties of itself


Apple uses the following struct to setup PresentationBackgroundInteraction:

public struct PresentationBackgroundInteraction : Sendable {
    public static var automatic: PresentationBackgroundInteraction { get }
    public static func enabled(upThrough detent: PresentationDetent) -> PresentationBackgroundInteraction
    ...
}

It can then be used inside a view modifier like this;

    .presentationBackgroundInteraction(.enabled(upThrough(.medium))
  1. But how does this work? Each call will instantiate an instance of the struct but how do we get the value that was assigned in another location? We need to have a reference to this instance somewhere.
  2. How does Apple know where to put this reference and use it?
  3. Why prefer this over an enum?

If I try to recreate the setup of Apple I run into a couple of problems. Having the function return itself of course creates infinite recursion;

public static func enabled(upThrough detent: PresentationDetent) -> PresentationBackgroundInteraction {
    .enabled(upThrough: detent)
}

Assigning it to an internal var can of course mitigate this issue.

However if I would then use it as a view modifier it always returns an instance of the struct rather than the value that I assigned. So I can never alter based on which value was passed to my view modifier.

Struct

public struct PresentationBackgroundInteractionRecreate : Sendable {
    public static var automatic: PresentationBackgroundInteractionRecreate = .automatic
}

View

EmptyView()
    .presentationBackgroundInteractionRecreate(.automatic)

View Modifier

extension View {
    public func presentationBackgroundInteractionRecreate(
        _ interaction: presentationBackgroundInteractionRecreate
    ) -> some View {
        // This now always returns; PresentationBackgroundInteractionRecreate()
        // How would I be able to access `automatic` here
        print(interaction) 
        return EmptyView()
    }
}

Solution

  • Based on some basic reverse-engineering with Mirrors and dumping things out, I've found out that PresentationBackgroundInteraction is basically a wrapper around an enum, like this:

    public struct PresentationBackgroundInteractionRecreate: Hashable, Sendable {
        internal enum Kind: Hashable {
            case automatic
            case disabled
            case enabled(upThrough: PresentationDetent?)
        }
        
        internal let kind: Kind
        
        internal init(kind: Kind) {
            self.kind = kind
        }
        
        public static let automatic = Self.init(kind: .automatic)
        public static let enabled = Self.init(kind: .enabled(upThrough: nil))
        public static let disabled = Self.init(kind: .disabled)
        
        public func enabled(upThrough detent: PresentationDetent) -> Self {
            .init(kind: .enabled(upThrough: detent))
        }
    }
    

    The internal parts might even be fileprivate, for all I know.

    The view modifier can trivially read the kind. Reflecting the view returned by presentationBackgroundInteraction, it seems to use transformPreference, so I'd imagine the view modifier to look something like this:

    extension View {
        func presentationBackgroundInteractionRecreate(_ interaction: PresentationBackgroundInteractionRecreate) -> some View {
            transformPreference(PresentationOptionsPreferenceKey.self) { value in
                switch interaction.kind {
                    // change "value" in different ways
                }
            }
        }
    }
    

    PresentationOptionsPreferenceKey is another internal/fileprivate type. I don't know what the preference key's Value type (i.e. the type of the value closure parameter) is, but the point stands.

    Then I'd imagine some other parts of SwiftUI would read the preference (again using internal APIs) and set up the UISheetPresentationController according to those options.

    If you were to recreate something similar, you could also create your own PreferenceKey, and so on.


    I can think of some advantages of using structs instead of enums. Using an enum would expose a lot more implementation details than a struct would.

    An enum immediately gives away how many different cases it has, and that each case is different, whereas with a struct, .automatic might as well just return .disabled under the hood and it'd be "fair game".

    Using a struct also prevents people from exhaustively switching on its different cases. This makes adding more types of background interaction easier in the future. On the other hand, an enum strongly suggests to the developer that this is a fixed set of values.