- Published on
iOS 17'da SwiftUI'ning yangi ScrollView modifikatorlari: Paging, containerRelativeFrame, scrollTransition
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
iOS 17 SwiftUI'ga ScrollView uchun bir nechta kuchli yangi modifikator qo'shdi. Ular TikTok va Instagram Reels kabi to'liq ekranli paging, App Store'dagi gorizontal pager, va silliq animatsiyali scroll o'tishlar yaratishga imkon beradi — bularning barchasi oldin SwiftUI'da mavjud bo'lmagan edi.
Talab: iOS 17+
Boshlang'ich tuzilma
@available(iOS 17, *)
struct ScrollViewPagingBootcamp: View {
var body: some View {
ScrollView {
VStack(spacing: 30) {
ForEach(0..<10) { index in
Rectangle()
.frame(width: 300, height: 300)
.overlay(
Text("\(index)")
.foregroundColor(.white)
)
}
}
}
}
}
1. scrollBounceBehavior — bounce'ni boshqarish
Standart holda scroll view har doim "bouncing" (sakrash) effektiga ega. Faqat kontent scroll view hajmidan katta bo'lganda bouncing bo'lishini xohlasak:
ScrollView {
...
}
.scrollBounceBehavior(.basedOnSize)
.always— har doim bounce.basedOnSize— faqat kontent scroll view hajmidan katta bo'lsa bounce.automatic— standart (har doim bounce)
Kichik ro'yxatlar uchun .basedOnSize — eng qulay tanlov.
2. scrollTargetLayout + scrollTargetBehavior — to'xtash nuqtasini boshqarish
Standart holda scroll view fizika qonunlariga muvofiq istalgan joyda to'xtaydi. Buni o'zgartirish uchun:
ScrollView {
VStack(spacing: 30) {
ForEach(0..<10) { index in
Rectangle()
.frame(width: 300, height: 300)
}
}
.scrollTargetLayout() // qaysi layout bo'yicha hizalanadi
}
.scrollTargetBehavior(.viewAligned) // yoki .paging
.viewAligned— scroll to'xtaganda eng yaqin elementning yuqori qismi ekran boshiga hizalanadi.paging— TikTok uslubi: har bir sürükleme bir "sahifaga" o'tadi
3. containerRelativeFrame — TikTok pager (to'liq ekranli)
Paging'ni to'liq ekranli qilish uchun har bir katak scroll view hajmiga to'liq mos bo'lishi kerak:
ScrollView {
VStack(spacing: 0) { // spacing = 0 muhim!
ForEach(0..<10) { index in
Rectangle()
.ignoresSafeArea()
.containerRelativeFrame(.vertical) // vertikal o'qqa to'ldiradi
.overlay(
Text("\(index)")
.foregroundColor(.white)
)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.paging)
.ignoresSafeArea() // scroll view ham safe area'ni e'tiborsiz qoldirsind
Muhim: Spacing katakka tashqarida emas, ichida berilishi kerak (padding sifatida), aks holda hizalanish buziladi:
Rectangle()
.containerRelativeFrame(.vertical)
.padding(.vertical, 10) // ichida padding — to'g'ri
4. Gorizontal pager
ScrollView(.horizontal) {
HStack {
ForEach(0..<10) { index in
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 300)
.padding(10) // spacing ichida
.containerRelativeFrame(.horizontal) // gorizontal o'qqa to'ldiradi
.overlay(
Text("\(index)")
.foregroundColor(.white)
)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.paging) // yoki .viewAligned
.viewAligned bilan containerRelativeFrame olib tashlasak ham, scroll to'xtaganda eng yaqin katakka avtomatik hizalanadi.
5. scrollPosition — dasturiy scroll
@State private var scrollPosition: Int? = nil
VStack {
Button("Random scroll") {
withAnimation(.smooth) {
scrollPosition = (0..<20).randomElement()
}
}
ScrollView(.horizontal) {
HStack {
ForEach(0..<20) { index in
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 300)
.id(index) // ID element bilan mos
}
}
.scrollTargetLayout()
}
.scrollPosition(id: $scrollPosition, anchor: .center)
.scrollTargetBehavior(.viewAligned)
}
id turi scrollPosition turi bilan mos bo'lishi kerak — bu yerda ikkalasi ham Int. Identifiable model ishlatilsa — model turi beriladi.
6. scrollTransition — kataklarning animatsiyali paydo bo'lishi
Kataklarni scroll'da ekranga chiqayotganda animatsiya:
Rectangle()
.frame(width: 300, height: 300)
.scrollTransition { content, phase in
content
.opacity(phase.isIdentity ? 1 : 0)
.offset(y: phase.isIdentity ? 0 : -200)
}
Phase holatlari:
.identity— katak to'liq ekranda.topLeading— ekranning yuqori/chap tomonidan kirmoqda.bottomTrailing— ekranning pastki/o'ng tomonidan chiqmoqda
phase.isIdentity — katak to'liq ko'rinadimi?
scrollTransition konfiguratsiyasi
.scrollTransition(.interactive) { content, phase in // har bir pixel
content.opacity(phase.isIdentity ? 1 : 0)
}
.scrollTransition(.animated) { content, phase in // bir marta "yonadi/o'chadi"
content.opacity(phase.isIdentity ? 1 : 0)
}
.scrollTransition(.identity) { content, phase in // animatsiya yo'q
content
}
Threshold — qachon animatsiya boshlansing
.scrollTransition(.interactive(threshold: .visible(0.9))) { content, phase in
content.opacity(phase.isIdentity ? 1 : 0)
}
.visible(0.1)— 10% ko'rinaydigan bo'lishi bilanoq animatsiya boshlanadi.visible(0.9)— 90% ko'rinaydigan bo'lganda animatsiya tugaydi (uzoqroq davom etadi)
Barcha modifikatorlar birgalikda
@available(iOS 17, *)
struct FullExampleView: View {
@State private var scrollPosition: Int? = nil
var body: some View {
VStack {
Button("Scroll to random") {
withAnimation(.smooth) {
scrollPosition = (0..<10).randomElement()
}
}
ScrollView {
VStack(spacing: 0) {
ForEach(0..<10) { index in
Rectangle()
.containerRelativeFrame(.vertical)
.ignoresSafeArea()
.id(index)
.scrollTransition(.interactive) { content, phase in
content
.opacity(phase.isIdentity ? 1 : 0.3)
.scaleEffect(phase.isIdentity ? 1 : 0.8)
}
.overlay(
Text("\(index)")
.foregroundColor(.white)
.font(.largeTitle)
)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.paging)
.scrollBounceBehavior(.basedOnSize)
.scrollPosition(id: $scrollPosition)
.ignoresSafeArea()
}
}
}
Xulosa
| Modifikator | Vazifasi |
|---|---|
.scrollBounceBehavior(.basedOnSize) | Faqat zarur bo'lganda bounce |
.scrollTargetLayout() | Layout hizalanish uchun belgilash |
.scrollTargetBehavior(.paging) | TikTok uslubi paging |
.scrollTargetBehavior(.viewAligned) | Eng yaqin elementga hizalanish |
.containerRelativeFrame(.vertical) | Katak to'liq ekranga to'lsin |
.scrollPosition(id:) | Dasturiy animatsiyali scroll |
.scrollTransition { } | Ekranga chiqish animatsiyasi |
Bu modifikatorlar avval ScrollViewReader, GeometryReader va maxsus kodlar talab qilgan ko'plab UI pattern'larni bir necha qator kodga tushiradi.