Published on

SwiftUI-da ScrollViewReader yordamida avtomatik scroll qilish

Authors

Oddiy ScrollViewda biz qo'lda tepaga-pastga aylantirib ko'rishimiz mumkin, ammo uni avtomatik ravishda muayyan elementga scroll qila olmaymiz. Aynan shuning uchun ScrollViewReader kerak bo'ladi.

Buning eng keng tarqalgan amaliy misoli β€” chat ilovasi. Barcha xabarlar scroll view ichida bo'ladi, va ekran ochilganda siz so'nggi xabarga (pastga) avtomatik scroll bo'lishini xohlaysiz β€” aks holda foydalanuvchi eng qadimgi xabardan boshlab ko'radi, bu esa yomon tajriba hisoblanadi.


Boshlang'ich: oddiy ScrollView

Avval yangi fayl yaratamiz: ScrollViewReaderBootcamp. Asosiy tuzilmani quramiz β€” 50 ta kartochkadan iborat scroll view:

ScrollView {
    ForEach(0..<50) { index in
        Text("This is item number \(index)")
            .font(.headline)
            .frame(height: 200)
            .frame(maxWidth: .infinity)
            .background(Color.white)
            .cornerRadius(10)
            .shadow(radius: 10)
            .padding()
    }
}

Canvas'da "Resume" qilsak, kartochkalarni ko'rib, qo'lda aylantirib ko'rish mumkin. Ammo ekran ochilganda avtomatik ravishda, masalan, 49-elementga o'tish imkoni yo'q β€” buning uchun ScrollViewReader kerak.


ScrollViewReader qo'shish

ForEachni ScrollViewReader ichiga joylaymiz. Closure parametrini proxy deb ataymiz:

ScrollView {
    ScrollViewReader { proxy in
        ForEach(0..<50) { index in
            Text("This is item number \(index)")
                .font(.headline)
                .frame(height: 200)
                .frame(maxWidth: .infinity)
                .background(Color.white)
                .cornerRadius(10)
                .shadow(radius: 10)
                .padding()
                .id(index)  // <-- muhim!
        }
    }
}

.id(index) β€” bu juda muhim: proxy har bir elementning qayerda turganini bilishi uchun, har bir elementga aniq id berish shart. Holbuki bermasak, proxy.scrollTo ishlamaydi.


proxy.scrollTo bilan muayyan elementga scroll qilish

ForEachdan oldin, ScrollViewReader ichiga tugma qo'shamiz:

Button("Click here to go to #49") {
    proxy.scrollTo(49, anchor: nil)
}

Tugmani bosganimizda, u bizni 49-elementga olib boradi. Anchor β€” ekranda shu element qaysi joyda ko'rsatilishi:

proxy.scrollTo(30, anchor: .top)     // element tepada ko'rinadi
proxy.scrollTo(30, anchor: .center)  // element markazda ko'rinadi
proxy.scrollTo(30, anchor: .bottom)  // element pastda ko'rinadi
proxy.scrollTo(30, anchor: nil)      // standart

withAnimation bilan silliq scroll

Hozircha element "sakrab" chiqadi. Silliq animatsiya uchun withAnimation ichiga olamiz:

Button("Click here to go to #30") {
    withAnimation(.spring()) {
        proxy.scrollTo(30, anchor: .center)
    }
}

Endi tugmani bosganimizda, ekran silliq tarzda 30-elementga scroll qiladi.


ScrollViewReader tashqarisidan boshqarish

Ko'p holatlarda tugma scroll view'dan tashqarida joylashgan bo'ladi. Bu holda proxyga bevosita murojaat qila olmaymiz. Buning uchun quyidagi yondashuv ishlatiladi:

@State var textFieldText: String = ""
@State var scrollToIndex: Int = 0

VStack qo'shib, tepaga TextField va tugma, pastga esa ScrollView joylashtiramiz:

VStack {
    HStack {
        TextField("Enter a number...", text: $textFieldText)
            .frame(height: 55)
            .border(Color.gray)
            .padding(.horizontal)
            .keyboardType(.numberPad)

        Button("Scroll") {
            if let index = Int(textFieldText) {
                scrollToIndex = index
            }
        }
    }

    ScrollView {
        ScrollViewReader { proxy in
            ForEach(0..<50) { index in
                Text("This is item number \(index)")
                    .font(.headline)
                    .frame(height: 200)
                    .frame(maxWidth: .infinity)
                    .background(Color.white)
                    .cornerRadius(10)
                    .shadow(radius: 10)
                    .padding()
                    .id(index)
            }
            .onChange(of: scrollToIndex) { newValue in
                withAnimation(.spring()) {
                    proxy.scrollTo(newValue, anchor: .top)
                }
            }
        }
    }
}

Mantiqning tahlili:

  1. Foydalanuvchi TextFieldga son kiritadi va "Scroll" tugmasini bosadi.
  2. Tugma textFieldTextni Intga aylantiradi (if let orqali xavfsiz). Agar son bo'lmagan matn kiritilsa β€” crash bo'lmaydi, shunchaki scroll qilinmaydi.
  3. scrollToIndex yangilanadi.
  4. .onChange(of: scrollToIndex) shu o'zgarishni sezib, proxy.scrollTo(newValue, anchor: .top)ni chaqiradi.

.keyboardType(.numberPad) β€” foydalanuvchi faqat raqam kiritishi uchun.


Xavfsizlik haqida

proxy.scrollTo aqlli: agar berilgan id scroll view ichida mavjud bo'lmasa (masalan, 4000 berilsa, ammo faqat 0-49 bor), u hech narsa qilmaydi, crash bermaydi. Bu xatti-harakatni esda tuting.


Eng keng tarqalgan ishlatish holati

Haqiqiy ilovalarda ForEach ko'pincha 0..<50 emas, balki ma'lumotlar massivi bo'ladi. Shu holda har bir elementning indexini id sifatida berib, scroll view'ni so'nggi elementga yo'naltirish mumkin:

.onAppear {
    withAnimation(.spring()) {
        proxy.scrollTo(dataArray.indices.last, anchor: .bottom)
    }
}

Bu β€” chat ilovalarida ekran ochilishi bilan eng so'nggi xabarga avtomatik o'tishning klassik usuli.

Buy mea coffee