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?
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("Hello")
.preference(???)
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>>
x
}
}
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 {
isEnabled
? content.preference(key: MyPreferenceKey.self, value: value)
: content.preference(value: 0)
}
}
extension View {
func myPreference(value: CGFloat) -> some View {
modifier(MyPreferenceViewModifer(value: value))
}
}
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 thefalse
part to inferkey
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?