Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

SwiftUI onPreferenceChange is not called

Here is my code for learning PreferenceKey. I’m trying to update title from child view and showing in parent view via extensions and modifiers but onPreferenceChange is not getting called.

MainView

struct MainView: View {
    @State var title: String = "DEF"
    
    var body: some View {
        VStack {
            Text(title)
                .padding()
                .font(.system(size: 20))
                .foregroundStyle(Color.black)
                .titleByKey(title: $title)
            ChildView()
        }
    }
}

struct ChildView: View {
    @State var title: String = ""
    
    var body: some View {
        TextField("Enter", text: $title)
            .padding()
            .font(.system(size: 20))
            .foregroundStyle(Color.black)
            .background(Color.gray)
            .updateTitleByKey(string: title)
    }
}

Custom PreferenceKey

import Foundation
import SwiftUI

struct TitleChangePreferenceKey: PreferenceKey {
    static var defaultValue: String = ""
    
    static func reduce(value: inout String, nextValue: () -> String) {
        value = nextValue()
    }
}

extension View {
    func updateTitleByKey(string: String) -> some View {
        print("UP 1", string)
        return preference(key: TitleChangePreferenceKey.self, value: string)
    }
    
    func titleByKey(title: Binding<String>) -> some View {
        print("UP 2", title)
        return modifier(TitleChangeModifier(title: title))
    }
}

struct TitleChangeModifier: ViewModifier {
    @Binding var title: String
    
    func body(content: Content) -> some View {
        content
            .onChange(of: title) { _, value in
                print("onChange", value)
            }
            .onPreferenceChange(TitleChangePreferenceKey.self) { value in
                print("onPreferenceChange", value)
                title = value
            }
    }
}

But neither onPreferenceChange nor onChange is called. Let me know what i missed?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

I assume that the purpose of using preferences here is only for learning about preference keys. If this is real code, you should just pass a Binding directly to ChildView, and not have such a convoluted approach.

Preferences propagate up the view hierarchy, not across siblings. .titleByKey(title: $title) would not detect any change if it is placed on the Text. Text has no preference.

You should place it on the ChildView, since the ChildView is the one that has the preference (set by updateTitleByKey).

var body: some View {
    VStack {
        Text(title)
            .padding()
            .font(.system(size: 20))
            .foregroundStyle(Color.black)
        ChildView()
            .titleByKey(title: $title)
    }
}

It also works if you put .titleByKey(title: $title) on the VStack, since it is the parent of ChildView.


Side note: I would implement the preference key with a default value of nil:

struct TitleChangePreferenceKey: PreferenceKey {
    static var defaultValue: String? { nil }
    
    static func reduce(value: inout String?, nextValue: () -> String?) {
        guard let next = nextValue() else { return }
        value = next
    }
}
.onPreferenceChange(TitleChangePreferenceKey.self) { value in
    guard let value else { return } // added
    print("onPreferenceChange", value)
    title = value
}

This is because the contract of defaultValue is that reduce(value: &x, nextValue: {defaultValue}) should not change x.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading