Published on

SwiftUI-da Mask yordamida 5 yulduzli reyting tugmasi yaratish

Authors

Mask modifikatori kamdan-kam ishlatiladigan, ammo juda qiziqarli va kuchli vosita. Biz ushbu videoda klassik 5 yulduzli reyting komponentini yasaymiz β€” ko'plab ilovalarda ko'rgan narsamizni. Mask qanday ishlaydi: pastki qatlam (yulduzlar) mavjud, ustiga rectangle qo'yamiz, va shu rectangle'ni pastki qatlamning shakliga moslashtirish uchun mask ishlatamiz β€” natijada rectangle faqat yulduzlar shaklidagi joyni ko'rsatadi.


1-qadam: Oddiy yulduzli reyting (masksiz)

Avval masksiz, oddiy usulda yaratib ko'ramiz:

@State var rating: Int = 3

var body: some View {
    HStack {
        ForEach(1..<6) { index in
            Image(systemName: "star.fill")
                .font(.largeTitle)
                .foregroundColor(rating >= index ? Color.yellow : Color.gray)
                .onTapGesture {
                    rating = index
                }
        }
    }
}

rating >= index β€” reytingdan kichik yoki teng bo'lgan barcha yulduzlarni sariq qiladi. Simulyatorda bosib ko'rsak, yulduzlar o'zgaradi, ammo sakrab o'zgaradi β€” animatsiya yo'q. Ko'plab ilova uchun bu yetarli, ammo biz mask yordamida buni ancha chiroyliroq qila olamiz.


2-qadam: starsView komponentini ajratish

HStackni alohida private varga chiqaramiz, va foreground rangini doim kulrang qilib qo'yamiz β€” keyinchalik rangni mask orqali boshqaramiz:

private var starsView: some View {
    HStack {
        ForEach(1..<6) { index in
            Image(systemName: "star.fill")
                .font(.largeTitle)
                .foregroundColor(Color.gray)
                .onTapGesture {
                    withAnimation(.easeInOut) {
                        rating = index
                    }
                }
        }
    }
}

bodyda esa:

var body: some View {
    ZStack {
        starsView
            .overlay(overlayView.mask(starsView))
    }
}

3-qadam: overlayView β€” reyting kengligidagi rectangle

overlayView β€” bu yulduzlar ustiga qo'yiladigan sariq rectangle. Uning kengligi reytingga qarab o'zgaradi. Aniq kenglikni olish uchun GeometryReader kerak:

private var overlayView: some View {
    GeometryReader { geometry in
        ZStack(alignment: .leading) {
            Rectangle()
                .foregroundColor(Color.yellow)
                .frame(width: CGFloat(rating) / 5 * geometry.size.width)
        }
    }
    .allowsHitTesting(false)
}

Mantiq:

  • CGFloat(rating) / 5 β€” reytingning nisbati (masalan, 3/5 = 0.6)
  • * geometry.size.width β€” to'liq kenglikning shu nisbatdagi qismi
  • ZStack(alignment: .leading) β€” rectangle chap tomondan boshlanadi
  • .allowsHitTesting(false) β€” overlay qatlamiga bosish o'tib, pastdagi yulduzlarga boradi (quyida tushuntirish)

4-qadam: Mask qo'llash

var body: some View {
    ZStack {
        starsView
            .overlay(
                overlayView
                    .mask(starsView)
            )
    }
}

Nima sodir bo'ladi:

  1. starsView β€” kulrang yulduzlar (pastki qatlam)
  2. overlayView β€” sariq rectangle (ustki qatlam), kengligi reytingga qarab o'zgaradi
  3. .mask(starsView) β€” sariq rectangle'ni yulduzlar shakliga moslashtiradi

Natijada: sariq rang faqat yulduz shaklida ko'rinadi, va kenglik reytingga qarab chap tomondan to'ldiriladi.


allowsHitTesting nima uchun kerak

.overlay qo'shilganda, ustki qatlam (overlayView) pastki qatlamning ustini yopadi β€” onTapGesturelar endi ishlamay qoladi, chunki foydalanuvchi overlay'ni bosadi, yulduzlarni emas. .allowsHitTesting(false) esa overlay'ni "shaffof" qiladi β€” bosishlar u orqali o'tib, pastdagi starsViewga boradi.


To'liq kod

struct MaskBootcamp: View {

    @State var rating: Int = 3

    var body: some View {
        ZStack {
            starsView
                .overlay(
                    overlayView
                        .mask(starsView)
                )
        }
    }

    private var starsView: some View {
        HStack {
            ForEach(1..<6) { index in
                Image(systemName: "star.fill")
                    .font(.largeTitle)
                    .foregroundColor(Color.gray)
                    .onTapGesture {
                        withAnimation(.easeInOut) {
                            rating = index
                        }
                    }
            }
        }
    }

    private var overlayView: some View {
        GeometryReader { geometry in
            ZStack(alignment: .leading) {
                Rectangle()
                    .foregroundColor(Color.yellow)
                    .frame(width: CGFloat(rating) / 5 * geometry.size.width)
            }
        }
        .allowsHitTesting(false)
    }
}

Live Preview'da sinab ko'rsak: yulduzni bosganimizda, sariq rang chap tomondan silliq animatsiya bilan to'ldiriladi yoki qisqaradi β€” bu juda professional ko'rinadi.


Bonus: Gradient qo'shish

Rectangle'ni sariq rangda qoldirish shart emas β€” unga gradient ham qo'shishimiz mumkin:

Rectangle()
    .fill(
        LinearGradient(
            gradient: Gradient(colors: [.red, .blue]),
            startPoint: .leading,
            endPoint: .trailing
        )
    )
    .frame(width: CGFloat(rating) / 5 * geometry.size.width)

Yulduzlar endi chapdan qizil, o'ngdan ko'k rangda to'ladi β€” atigi bir necha qator kod o'zgartirish bilan ajoyib effekt.


Xulosa

Mask ishlash mantig'i:

  1. Pastki qatlam β€” asl view (yulduzlar)
  2. Ustki qatlam β€” moslashtiriladigan view (rectangle)
  3. .mask(pastki_qatlam) β€” ustki qatlamni pastki qatlamning shakliga kesadi

Kenglik o'zgartirilganda mask ham mos ravishda o'zgaradi β€” natijada chiroyli, animatsiyalangan reyting komponenti yaratiladi.

Buy mea coffee