- Published on
SwiftUI-da DragGesture yordamida elementlarni ko'chirish, sudrab harakatlantirish va yuqoriga siljitish
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
DragGesture — bu kursda ko'rib chiqayotgan gestlar (harakatlar) orasida eng muhimi va eng keng tarqalgani. Uni implementatsiya qilish boshqalariga nisbatan biroz murakkabroq bo'lishi mumkin, ammo biz ushbu videoda ikki xil real hayotiy misolni ko'rib chiqamiz — Tinder kabi kartochkani chapga-o'ngga siljitish va pastdan yuqoriga ochiladigan ekran — shunday qilib siz buni o'z ilovalaringizda osongina amalga oshira olasiz.
Asosiy DragGesture
Yangi fayl yaratamiz: DragGestureBootcamp. Canvas'ni ulab, kodlashni boshlaymiz.
Asosiy tushunchadan boshlaylik. RoundedRectangle(cornerRadius: 20) qo'shamiz, unga 100x100 frame beramiz, va .gesture(DragGesture()) qo'shamiz:
@State var offset: CGSize = .zero
var body: some View {
RoundedRectangle(cornerRadius: 20)
.frame(width: 100, height: 100)
.offset(offset)
.gesture(
DragGesture()
.onChanged { value in
withAnimation(.spring()) {
offset = value.translation
}
}
.onEnded { value in
withAnimation(.spring()) {
offset = .zero
}
}
)
}
value.translation — bu joriy pozitsiya bilan qo'l qo'yilgan joy orasidagi masofa, va u CGSize turida keladi. Shuning uchun yuqorida @State var offset: CGSize = .zero yaratamiz va uni RoundedRectanglega .offset(offset) sifatida qo'llaymiz.
onChangedda offsetni value.translationga, onEndedda esa .zeroga qaytaramiz — shunday qilib element qo'yib yuborilganda boshlang'ich joyiga spring animatsiyasi bilan "sakrab" qaytadi. .zero — CGSize(width: 0, height: 0)ning qisqartmasi.
Simulyatorda sinab ko'rsak: bir barmoq bilan bosib, sudrab harakatlantira olamiz, qo'yib yuborganimizda esa chiroyli animatsiya bilan qaytib keladi.
1-misol: Tinder kabi kartochka (scale + rotation)
Endi shu kartochkani 300x500 o'lchamga keltiraman — bu darhol kartochkaga o'xshab qoladi:
RoundedRectangle(cornerRadius: 20)
.frame(width: 300, height: 500)
.offset(offset)
.scaleEffect(getScaleAmount())
.rotationEffect(.degrees(getRotationAmount()))
.gesture(...)
Kartochka chapga-o'ngga siljigan sari biroz kichrayib va burilib ketishini xohlaymiz (Tinder va Bumble'dagi kabi). Buning uchun ikkita funksiya yaratamiz.
getScaleAmount()
func getScaleAmount() -> CGFloat {
let max = UIScreen.main.bounds.width / 2
let currentAmount = abs(offset.width)
let percentage = currentAmount / max
return 1.0 - min(percentage, 0.5) * 0.5
}
Izoh:
max— ekranning yarmiga teng (kartochka eng ko'pi bilan shuncha siljiy oladi)currentAmount— mutlaq qiymat (abs) — chapga yoki o'ngga bo'lishi muhim emaspercentage— kartochka chekkaga qanchalik yaqin ekani (0 dan 1 gacha)min(percentage, 0.5) * 0.5— kichrayish tezligini sekinlashtiradi, 0.5 dan oshmaydigan qilib cheklaydi
getRotationAmount()
func getRotationAmount() -> Double {
let max = UIScreen.main.bounds.width / 2
let currentAmount = offset.width
let percentage = currentAmount / max
let percentageAsDouble = Double(percentage)
let maxAngle: Double = 10
return percentageAsDouble * maxAngle
}
Bu safar abs ishlatmaymiz — chapga ketsa manfiy, o'ngga ketsa musbat bo'lishi kerak, shunday qilib kartochka harakatga mos yo'nalishda buriladi. Maksimal burchak — 10 daraja.
Canvas'ni qayta ishga tushirib ko'rsak: kartochkani o'ngga siljitganda u biroz kichrayadi va o'ng tomonga buriladi; chapga siljitganda esa aksincha. Bu — Tinder yoki Bumble'dagi effektga juda yaqin ko'rinadi, va buni UIKit'da qilishga nisbatan ancha sodda.
2-misol: Pastdan yuqoriga siljituvchi ekran (Swipe Up Sheet)
Endi yangi fayl yaratamiz: DragGestureBootcamp2.
Avval ekran tuzilmasini qurish kerak. ZStack qo'shamiz, fon sifatida Color.green.ignoresSafeArea() qo'yamiz, va ustiga maxsus "Sign Up" panelini joylashtiramiz:
// MySignUpView — alohida extracted view
struct MySignUpView: View {
var body: some View {
VStack(spacing: 20) {
Image(systemName: "chevron.up")
.padding(.top)
Text("Sign Up")
.font(.headline)
.fontWeight(.semibold)
Image(systemName: "flame.fill")
.resizable()
.scaledToFit()
.frame(width: 100, height: 100)
Text("This is the description for our app. This is my favorite SwiftUI course and I recommend it to all of my friends.")
.multilineTextAlignment(.center)
Text("Create an account")
.foregroundColor(.white)
.font(.headline)
.padding()
.padding(.horizontal)
.background(Color.black)
.cornerRadius(10)
Spacer()
}
.frame(maxWidth: .infinity)
.background(Color.white)
.cornerRadius(30)
}
}
Offset bilan pastga surib boshlash
Panel avvalboshdan ekranning pastida, ko'rinmaydigan joyda turishi kerak. Buning uchun startingOffsetYni ekran balandligining **85%**iga tenglaymiz:
@State var startingOffsetY: CGFloat = UIScreen.main.bounds.height * 0.85
@State var currentDragOffsetY: CGFloat = 0
@State var endingOffsetY: CGFloat = 0
MySignUpViewga uchta offset qo'llaymiz:
MySignUpView()
.offset(y: startingOffsetY)
.offset(y: currentDragOffsetY)
.offset(y: endingOffsetY)
DragGesture qo'shish
.gesture(
DragGesture()
.onChanged { value in
withAnimation(.spring()) {
currentDragOffsetY = value.translation.height
}
}
.onEnded { value in
withAnimation(.spring()) {
if currentDragOffsetY < -150 {
// Yuqoriga yetarli darajada siljitildi — panelni "qulflaymiz"
endingOffsetY = -startingOffsetY
} else if endingOffsetY != 0 && currentDragOffsetY > 150 {
// Panel yuqorida turibdi, pastga yetarli siljitildi — qaytaramiz
endingOffsetY = 0
}
currentDragOffsetY = 0
}
}
)
Mantiqning tahlili:
onChangedda faqat currentDragOffsetYni yangilaymiz — bu foydalanuvchi qo'lini siljitayotganda panelning "jonli" harakatini ta'minlaydi.
onEndedda uch holat tekshiriladi:
currentDragOffsetY < -150— panel yuqoriga 150 pikseldan ko'proq siljitildi → uni yuqorida "quflaymiz":endingOffsetY = -startingOffsetY(bu ikkita offset bir-birini bekor qiladi, panel yuqori chekkaga taqaladi).endingOffsetY != 0 && currentDragOffsetY > 150— panel yuqorida turibdi (endingOffsetY noldan farqli) va foydalanuvchi pastga 150 pikseldan ko'proq siljitdi → panelni pastga qaytaramiz:endingOffsetY = 0.Har ikki holatda ham
currentDragOffsetY = 0— bu har doim ishlaydi, shuning uchun uniif/else ifdan tashqarida yozish kodimizni tozalaydi.
To'liq DragGestureBootcamp2 kodi
struct DragGestureBootcamp2: View {
@State var startingOffsetY: CGFloat = UIScreen.main.bounds.height * 0.85
@State var currentDragOffsetY: CGFloat = 0
@State var endingOffsetY: CGFloat = 0
var body: some View {
ZStack {
Color.green
.ignoresSafeArea()
MySignUpView()
.offset(y: startingOffsetY)
.offset(y: currentDragOffsetY)
.offset(y: endingOffsetY)
.gesture(
DragGesture()
.onChanged { value in
withAnimation(.spring()) {
currentDragOffsetY = value.translation.height
}
}
.onEnded { value in
withAnimation(.spring()) {
if currentDragOffsetY < -150 {
endingOffsetY = -startingOffsetY
} else if endingOffsetY != 0 && currentDragOffsetY > 150 {
endingOffsetY = 0
}
currentDragOffsetY = 0
}
}
)
}
.ignoresSafeArea(edges: .bottom)
}
}
Canvas'da "Resume" tugmasini bossak: panel ekranning pastida turadi; yuqoriga yetarli siljitganimizda, u spring animatsiyasi bilan tepaga "qulflaydi". Tepadan pastga yetarli siljitganimizda esa, u qaytadan pastga qaytadi.
Xulosa
Ushbu videoda ikki xil real hayotiy DragGesture misolini ko'rdik:
1-misol — Tinder kabi kartochka: offset, scaleEffect va rotationEffectni DragGesture bilan birlashtirish orqali tabiiy va chiroyli UI effekti yaratdik.
2-misol — Pastdan yuqoriga siljituvchi panel: uch xil @State o'zgaruvchi (startingOffsetY, currentDragOffsetY, endingOffsetY) yordamida panelni ikki holatda boshqardik — "pastda" va "yuqorida". Spring animatsiyasi va oddiy matematik tekshiruvlar bilan bu UIKit'ga nisbatan ancha kam kod bilan amalga oshirildi.
Ilovangizda ko'plab foydalanuvchi interfeyslarini qurayotganingizda, bu mantiqlar juda foydali bo'ladi.