read

Preference is the way to pass data from children to parent.

On the other hand, environment passes data from parent to children.

Custom Preference

The steps to creating a preference include creating a preference key (with type and default), then the children set value with the key.

/// 1. Declare a preference key
struct SomePreference: PreferenceKey {
    /// The type is CGSize here. It can be any type you want.
    static var defaultValue: CGSize { return .zero }

    /// Because multiple children can use the same key, a reduce function will pass it up to parent.
    /// This implementation will reduce to return the min size.
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        let nextValue = nextValue()
        let minWidth = min(value.width, nextValue.width)
        let minHeight = min(value.height, nextValue.height)
        value = CGSize(width: minWidth, height: minHeight)
    }
}

struct SomeView: View {
    var body: some View {
        VStack {
            /// 2. Children will set it
            GeometryReader() { g in
                Text("I gonna pass my size to my parent")
                    .preference(key: SomePreference.self, value: g.size)
            }
            GeometryReader() { g in
                Text("Me too!")
                    .preference(key: SomePreference.self, value: g.size)
            }
        }
        .onPreferenceChange(SomePreference.self) { width in
            // 3. Parent receives it
            print("Preference changed: \(width)")
        }
    }
}

NOTE: The reduce function is required because multiple children gonna pass it to a parent. The function is to reduce to a single value.

But if you don’t want to reduce, you could read the next section.

Collection Preference

If you don’t want to reduce, then a way to pass every children data is to make the type an array, then simply append values to it.

struct CollectionPreference: PreferenceKey {
    static var defaultValue: [CGSize] { return [] }
    static func reduce(value: inout [CGSize], nextValue: () -> [CGSize]) {
        value.append(contentsOf: nextValue())
    }
}

Image

@samwize

¯\_(ツ)_/¯

Back to Home