0

I just made a function for modifying views, the code builds itself, but when I am using it it complaints about Type issue related to protocol, it must be an easy fix, but I could not solve it.

struct CGSizePreferenceKey: PreferenceKey {
    
    static var defaultValue: CGSize { get { return CGSize() } }
    
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() }
    
}

extension View {
    
    func onPreference<K: PreferenceKey>(key: K, value: K.Value, action: ((K.Value) -> Void)?) -> some View where K.Value: Equatable {
        return self
            .preference(key: K.self, value: value)
            .onPreferenceChange(K.self, perform: { newValue in action?(newValue)})
    }
}

Use case:

struct ContentTestView: View {
    
    @State private var size: CGSize = .zero
    
    var body: some View {
        
        Button("update") { size = CGSize(width: 100, height: 100) }
         .onPreference(key: CGSizePreferenceKey, value: size, action: { newValue in print(newValue) })
    }
}

Errors:

Instance method 'onPreference(key:value:action:)' requires that 'CGSizePreferenceKey.Type.Value' conform to 'Equatable'

Cannot convert value of type 'CGSize' to expected argument type 'CGSizePreferenceKey.Type.Value'

1
  • 1
    Updated in question, thanks. @Cristik Commented Mar 1, 2022 at 13:46

1 Answer 1

1

First things first, you should change the definition of onPreference to accept a meta-type instead of an instance of K, the simple reason being that you never use that instance of K, SwiftUI doesn't ask for an instance of PreferenceKey in that context.

extension View {    
    func onPreference<K: PreferenceKey>(key: K.Type, value: K.Value, action: @escaping (K.Value) -> Void) -> some View where K.Value: Equatable {

I also took the liberty of removing the optionality of the action parameter, since it makes little sense to call onPreference without a callback.

Now, with the new definition in place, what's left is to update the call site to pass the meta-type:

.onPreference(key: CGSizePreferenceKey.self, ...

Note that a shorter solution would've been to just instantiate the preference at the call site:

.onPreference(key: CGSizePreferenceKey(), ...

, that would've make the compiler happy, however it wouldn't have been the right solution.

Sign up to request clarification or add additional context in comments.

4 Comments

Your code is working, but I did not understand how your answer solved the issue? For you may is a simple issue, but for me it is not, As you can see the SwiftUI modifier of preference needs a type that conform to PreferenceKey, and I just satisfied it, the things are not clear for me why I need use Type in code. It is not about copy and paste the answer, I want understand it.
@Mada as I explained in the answer,onPreference was requiring an instance of that type K as the first parameter, which didn't make much sense as SwiftUI doesn't ask for instances, it only asks for the type itself.
Thanks, now I have a problem to understand when some parameter asking for an instance of a protocol or the protocol itself, is there a way to know about it?
Well, that's easy, @Mada, if function signature asks for K it needs an instance, if it asks for K.Type is needs a meta-type. .Type denotes the need for a meta-type instead of an instance.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.