SwiftUI `func preference<K>(key: K.Type = K.self, value: K.Value)`: why is the first param has this default? How to make use of default? I cannot omit the first param

Usually with K.Type = K.self parameter, it can be omitted and use type inference. But in this case here, I can find no way to omit this parameter. Why is it written this way?

See Apple Developer Documentation

Because in normal usage there's no context for inference of the conforming type, K, only its Value from the passed value. However, if you fully declared your View type rather than some View, it could infer K from that view type.

K : PreferenceKey

How can a View type get to K? For example:


Text is not opaque here, the type is known. Can you give me an example on how to omit preference() first parameter?

Grab the type of Text(...).preference(...) by assigning it to a value and see what the compiler infers. Then you can use that same type to provide an inference context that would enabled to drop the key type.

I fully specify the type, but still Generic parameter 'K' could not be inferred:

import SwiftUI

struct MyPreferenceKey: PreferenceKey {
    static var defaultValue = CGFloat.zero
    static func reduce (value: inout CGFloat, nextValue: () -> CGFloat) {
        value = max(value, nextValue())

struct ContentView: View {
    var body: some View {
        let x: ModifiedContent<Text, _PreferenceWritingModifier<MyPreferenceKey>> = Text("Hello").preference(value: 0) as! ModifiedContent<Text, _PreferenceWritingModifier<MyPreferenceKey>>   // Generic parameter 'K' could not be inferred
        let _ = print(type(of: x))  // prints: ModifiedContent<Text, _PreferenceWritingModifier<MyPreferenceKey>>

There should no need for the as! there. But I don't know, perhaps erasing through some doesn't allow the inference to work.

@young Here's an example where you can omit the key parameter

struct MyPreferenceViewModifer: ViewModifier {
  var value: CGFloat
  @Environment(\.isEnabled) private var isEnabled

  func body(content: Content) -> some View {
      ? content.preference(key: MyPreferenceKey.self, value: value)
      : content.preference(value: 0)

extension View {
  func myPreference(value: CGFloat) -> some View {
    modifier(MyPreferenceViewModifer(value: value))
1 Like

Without the as!, then two errors there:

Cannot convert value of type 'some View' to specified type 'ModifiedContent<Text, _PreferenceWritingModifier<MyPreferenceKey>>'

View modifiers all return some View, so have to have as! to get the real type back?

@iampatbrown thanks for the example, so the true part of the ternary allows for the false part to infer key.

so the true part of the ternary allows for the false part to infer key

In that example yeah. It was more to demonstrate that it's possible. I'm unsure how practical it is. I've never needed to use it. The below would normally be my first choice.

content.preference(key: MyPreferenceKey.self, value: isEnabled ? value : 0)

Here are some other examples:

extension View {
  func myPreference(value: CGFloat?) -> some View {
    value.map { preference(key: MyPreferenceKey.self, value: $0) } ?? preference(value: 0)

struct ContentView: View {
  var body: some View {
    var view = Text("Hello").preference(key: MyPreferenceKey.self, value: 0)
    view = Text("Goodbye").preference(value: 42)
    return view

That's really interesting. I knew that some View had to be the "same" type within the function, and I knew that the concrete type at run time could take generic parameters into account, but I didn't realize the compiler would understand that, and allow value.map { preference(key: A.self, value: 0) } ?? preference(key: A.self, value: 1) but not value.map { preference(key: A.self, value: 0) } ?? preference(key: B.self, value: 1).

Okay, so view gets its type then when re-assign key can be inferred. This code is not "practical" I would say and when they make the API, they must be intended for actually practical use case?

The entire suite of these API:

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 onPreferenceChange<K>(_ key: K.Type = K.self, perform action: @escaping (K.Value) -> Void) -> some View where K : PreferenceKey, K.Value : Equatable
func overlayPreferenceValue<Key, T>(_ key: Key.Type = Key.self, @ViewBuilder _ transform: @escaping (Key.Value) -> T) -> some View where Key : PreferenceKey, T : View
func backgroundPreferenceValue<Key, T>(_ key: Key.Type = Key.self, @ViewBuilder _ transform: @escaping (Key.Value) -> T) -> some View where Key : PreferenceKey, T : View

anchorPreference() is like preference(), I now know how key inference work. But overlayPreferenceValue() and backgroundPreferenceValue()? They are make overlay/background view. What do they have in mind for key inference for these two? By learning how to use these API's key default, I can gain insight about SwiftUI.

A and B are difference PreferenceKey so the resulting some View type are not the same?

First is something like ModifiedContent<SomeView, _PreferenceWritingModifier<A>>, second is ModifiedContent<SomeView, _PreferenceWritingModifier<B>>

So the types don't match?