SwiftUI | Warning: Bound preference _ tried to update multiple times per frame. Possible reasons?

Viewed 8894

Ever since I am working with preferences (PreferenceKey,..), I receive this message in the console:

Bound preference _ tried to update multiple times per frame.

After countless times of research, I haven't found any way to silence it. So... since there is yet no question specifically to this warning, what would you think are possible reasons?

If not, can this warning be ignored or did I have to fix it?

Thank you so much!

(I have tried to find an example, but somehow I didn't get any warnings with an easy one...)

3 Answers

The SwiftUI change handlers such as onPreferenceChange are called on an arbitrary thread. So if these changes impact your View, you should re-dispatch to ensure that you make those updates on the main thread:

.onPreferenceChange(MyPreferenceKey.self) { newValue in
    DispatchQueue.main.async {
        widget.value = newValue
    }
}

I think this answer from an Apple engineer describes the general issue:

It sounds like you have a cycle in your updates. For example, a GeometryReader that writes a preference, that causes the containing view to resize, which causes the GeometryReader to write the preference again. It’s important to avoid creating such cycles. Often that can be done by moving the GeometryReader higher in the view hierarchy so that its size doesn’t change and it can communicate size to its subviews instead of using a preference. I’m afraid I can’t give any more specific guidance than that without seeing your code, but hopefully, that helps you track down the issue!

https://www.bigmountainstudio.com/community/public/posts/65727-wwdc-2021-questions-answers-from-slack-the-unofficial-archive

At least it inspired me to solve the related bug in my case and make the warnings go away (almost :)).

I am building a calendar app, based on the month it set the choseDate as initial value, when not using main queue, it goes into an infinite loop with messages like onChange(of: String) action tried to update multiple times per frame. After change to use main queue solved my problem.

                DispatchQueue.main.async {
                    dateChosen = Date()
                }
Related