Published on

SwiftUI'da PreferenceKey bilan ishlash (Child'dan Parent'ga ma'lumot yuborish)

Authors

Oddiy oqim va PreferenceKey nima?

SwiftUI da ma'lumotlar doim tepadan pastga (Parent dan Child ga) qarab harakatlanadi. Masalan, siz Parent oqimda o'zgaruvchi yaratasiz va uni ichkaridagi Child View'ga @Binding yordamida uzatasiz.

Lekin, qachondir .navigationTitle("Sarlavha") ni ishlatib ko'rganmisiz? Nega biz uni Child View ichida yozamiz, lekin u tashqaridagi Parent NavigationBar ga borib tushadi? Sababi, u yerda orqa fonda PreferenceKey ishlaydi! PreferenceKey ichki View lardan olingan ma'lumotlarni yig'ib, tepadagi Parent ga uzatish xususiyatiga ega.

1-Misol: Oddiy Yozuvni (String) tepaga uzatish

Keling, shaxsiy (Custom) sarlavha PreferenceKey yasaymiz. Buning uchun PreferenceKey protokoliga bo'ysunadigan Struct yaratamiz. Bizga ikkita narsa: defaultValue (boshlang'ich qiymat) va reduce (keyingi qiymat kelganda nima qilish kerakligi) funksiyasi kerak bo'ladi.

import SwiftUI

struct CustomTitlePreferenceKey: PreferenceKey {
    static var defaultValue: String = ""

    static func reduce(value: inout String, nextValue: () -> String) {
        // Eski qiymatni yangi kelgan qiymat bilan yangilaymiz
        value = nextValue()
    }
}

Endi uni View da qanday eshitib va yuborish mumkinligini ko'ramiz:

struct PreferenceKeyBootcamp: View {
    @State private var parentText: String = "Boshlang'ich Matn"

    var body: some View {
        VStack {
            Text(parentText) // Bu matn tepadagi ota View'da turibdi
                .font(.largeTitle)

            // Lekin u o'zgarishni ichkaridan kutib oladi
            ChildView()
        }
        // Ichkaridan "CustomTitlePreferenceKey" kelishini kutib eshitib turadi
        .onPreferenceChange(CustomTitlePreferenceKey.self) { newTitle in
            self.parentText = newTitle
        }
    }
}

struct ChildView: View {
    var body: some View {
        Text("Bu child view")
            // Shunchaki o'zidan tepaga shu ma'lumotni Preference qilib faryod qiladi!
            .preference(key: CustomTitlePreferenceKey.self, value: "Ichkaridan kelgan matn!")
    }
}

2-Misol: Ichki elementning o'lchamini (CGSize) Parent'ga uzatish

Eng ko'p ishlatiladigan holat: Bizga ekrandagi biror qutichaning haqiqiy razmeri kerak, va biz uni GeometryReader orqali topib, tepadagi qandaydir elementning ham razmerini unga moslamoqchimiz.

struct RectangleSizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero

    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

// ... Qanday ishlatish mumkin:
Rectangle()
    .overlay(
        GeometryReader { geo in
            Color.clear
                .preference(key: RectangleSizePreferenceKey.self, value: geo.size)
        }
    )

Tepadagi Parent (ZStack yoki VStack) dagi biror matn esa huddi shu RectangleSizePreferenceKey ni tinglab o'tirishi (onPreferenceChange) orqali to'g'ridan-to'g'ri o'lchamni olib, o'zini shu razmerga moslab oladi!

3-Misol: ScrollView'da qancha skroll qilinganini (Offset) aniqlash

Eng qiziqarli joyi! Agar siz tepaga aylantirganingizda tepadan NavigationBar tushadigan yoki rasm xiralashadigan qilishni istasangiz, element qayerda joylashganini doimiy o'lchab tepaga uzatishingiz kerak.

struct ScrollOffsetPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

ScrollView ichidagi tepada joylashgan kichik (ko'rinmas) elementga buni beramiz:

Text("Sarlavha")
    .background(
        GeometryReader { geo in
            Color.clear
                .preference(key: ScrollOffsetPreferenceKey.self, value: geo.frame(in: .global).minY)
        }
    )

Endi Parent ScrollView da ushbu .onPreferenceChange(ScrollOffsetPreferenceKey.self) ni ushlab, minY qiymati qancha kichrayib ketganiga qarab (ya'ni tepaga qarab tushyaptimi) o'zingiz xohlagan animatsiya va UI larni ko'rsatishingiz yoki yashirishingiz mumkin bo'ladi!

Buy mea coffee