SwiftUI: anchorPreference<A, K>(key _: K.Type = K.self, ...): how to make use of key param's default value

func anchorPreference<A, K>(key _: K.Type = K.self, value: Anchor<A>.Source, transform: @escaping (Anchor<A>) -> K.Value) -> some View where K : PreferenceKey

func overlayPreferenceValue<Key, T>(_ key: Key.Type = Key.self, @ViewBuilder _ transform: @escaping (Key.Value) -> T) -> some View where Key : PreferenceKey, T : View

See the key parameter has this default value. But I cannot figure out how to write code to leverage this defalult. All the examples define a separate struct Xxx: PreferenceKey and pass Xxx.self to these call. I thought well, maybe the key default assume the View itself implements PreferenceKey, doing so I still cannot remove passing in the key parameter:

// Example from https://swiftwithmajid.com/2020/03/18/anchor-preferences-in-swiftui/


import SwiftUI

//fileprivate struct BoundsPreferenceKey: PreferenceKey {
//    typealias Value = Anchor<CGRect>?
//
//    static var defaultValue: Value = nil
//
//    static func reduce(
//        value: inout Value,
//        nextValue: () -> Value
//    ) {
//        value = nextValue()
//    }
//}

struct AnchorPreferencesExample: PreferenceKey, View {
    typealias Value = Anchor<CGRect>?

    static var defaultValue: Value = nil

    static func reduce(
        value: inout Value,
        nextValue: () -> Value
    ) {
        value = nextValue()
    }

    var body: some View {
        ZStack {
            Color.yellow
            Text("Hello World !!!\nYou okay?\nOr not?")
                .multilineTextAlignment(.center)
                .padding()
                .anchorPreference(
                    key: AnchorPreferencesExample.self,     // <<<<< but still cannot remove this param
                    value: .bounds
                ) { $0 }
        }
        //                       VVV--- and still  cannot remove this
        .overlayPreferenceValue(AnchorPreferencesExample.self) { preferences in
            GeometryReader { geometry in
                preferences.map {
                    Rectangle()
                        .stroke()
                        .frame(
                            width: geometry[$0].width,
                            height: geometry[$0].height
                        )
                        .offset(
                            x: geometry[$0].minX,
                            y: geometry[$0].minY
                        )
                }
            }
        }
    }
}


struct AnchorPreferencesExample_Previews: PreviewProvider {
    static var previews: some View {
        AnchorPreferencesExample()
    }
}

So how to make this example make use of the key default as the api intend?

The default value allows the compiler to infer the type from the context. In this case you could explicitly type the closure that uses the type to provide the context. More usefully you could pass a closure with a defined type as that parameter and the type would be inferred. It’s a nice way to be flexible around inference vs. explicit typing.

What are all the possible "context" :roll_eyes:? My understanding is when a default value is provided for a parameter in a method signature, it means this parameter can be omited in the right "context". But what's is this context? My problem is the generic type.

Can you explain with some examples, please?

IIRC, it was Key.Value being unique (no other Key share this Value type).

PS

I personally don’t like this kind of inference where Key doesn’t really appear in the signature, but only via Key.Value.

1 Like

Sure. By context here I mean the generic context, which is the information around your use or declaration that the compiler can use to infer the type. Say you had an asynchronous decoding method. Like other decoding methods you'd declare it generically:

func decode<T: Decodable>decode(_ completion: (T) -> Void)

Due to the use of the closure, Swift's type inference can't determine the type of T just from the context:

decode { decoded in // Error: Generic parameter 'T' could not be inferred.
    let string: String = decoded
}

Instead, you need to provide the type directly in the closure definition:

decode { (decoded: String) in
    let string = decoded
}

As you can see, this is fairly awkward, especially if the type is complex or uses other generic types. So we can offer a different API to make it a bit easier:

func decode<T: Decodable>(type: T.Type, _ completion: (T) -> Void)

This allows us to pass the type directly, separately from the completion handler.

decode(type: String.self) { decoded in
    let string = decoded
}

While that works fine, it does make the API more verbose, especially when the type could be otherwise inferred. It also limits the usage of the method to contexts where you can pass the type directly. So this becomes impossible:

func wrapped(_ completion: (String) -> Void) {
    decode(type: // How do I get the type?, ...)
}

The solution is to default the parameter to contextual generic type so that it can be used in either scenario:

func decode<T: Decodable>(type: T.Type = T.self, _ completion: (T) -> Void)

This allows you to successfully use it in both of the previous examples:

decode(type: String.self) { decoded in 
    let string = decoded
}

and

func wrapped(_ completion: (String) -> Void) {
    decode(completion)
}

In the second case, the complier knows the type you want decoded is String and can successfully pass it that information through to decode due to the use of the defaulted argument.

1 Like

:+1::pray:

Great explanation! I understand now. This seems to be common usage pattern but I don't remember this kind of use case is explained in the Language Guide.

Thank you!

I think the SwiftUI preference api is written to cover this use case only:

This allows us to pass the type directly, separately from the completion handler.

decode(type: String.self) { decoded in
    let string = decoded
}

this api do not need to cover second "wrapped" use case.

If this is the case, the .preference(key:,…) key don’t really need the default value because there is no way to omit it and infer K from value.

xxxxxxxxxzxxxxxxzzxxxxxxzzzzzzz

Original post....

Sorry to bother you again. I can't figure out how to apply the idea to:

func preference<K>(key _: K.Type = K.self, value: K.Value) -> some View where K : PreferenceKey

how to just pass in only value and let the compiler infer K? What am I doing wrong:

import SwiftUI

fileprivate struct Xxx: PreferenceKey {
    typealias Value = CGFloat
    static var defaultValue: Value = 0
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = nextValue()
    }
}

struct PreferenceKeyDefaultTypeInference: View {
    // Fully qualified type to value
    private static let value: Xxx.Value = 123
    
    var body: some View {
        Text("There we are!")   // Error here at this very line: Generic parameter 'K' could not be inferred
            .preference(value: Self.value)   // <<= not how to infer K?
    }
}

struct PreferenceKeyDefaultTypeInference_Previews: PreviewProvider {
    static var previews: some View {
        PreferenceKeyDefaultTypeInference()
    }
}

In your example, T is used in the closure so the compiler knows what T is from the fully spec'ed closure. Here in .preference(), the second param value type is K.Value: I don't see how the compiler can get the type of K from K.Value :frowning:

I mistakenly thought by using Xxx.Value as the type specifier, the compiler then knows both Xxx and .Value type info. But that's not the case. The compiler only knows about Xxx.Value, it cannot infer Xxx from Xxx.Value.

What do they have in mind when they wrote the .preference()/.anchorPreference() api with those key with default K.self?

Terms of Service

Privacy Policy

Cookie Policy