UIKit 11: Gesture Recognizers va Touch Hodisalari — State Machine, Konfliktlar va Hit-Testing
📱 UIKit va SwiftUI — chuqur
UIKit 11: Gesture Recognizers va Touch Hodisalari — State Machine, Konfliktlar va Hit-Testing
Holat: 2026-yil sentabr. Har qanday mobil interfeysning asosi — foydalanuvchining barmoq harakatlarini his qilishdir. Ammo ko'plab dasturchilar nima sababdan
UITableViewCellichidagi tugma bosilmay qolishi,UIScrollViewichidagi swipe yoki pan nega boshqa jest bilan to'qnashishi yoki kichik tugmalarni bosish nega noqulayligini chuqur bilmaydi. Ushbu darsdaUIEventvaUITouchsayohati, Gesture Recognizer State Machine,cancelsTouchesInViewtuzog'i vapoint(inside:with:)orqali katta Touch Target yasash texnikasi tahlil qilinadi.
Mundarija
- UIEvent va UITouch: Teginishning apparatdan ilovagacha sayohati
- UIResponder metodlari: touchesBegan, Moved, Ended, Cancelled
- UIGestureRecognizer arxitekturasi va State Machine
- cancelsTouchesInView va delaysTouchesBegan mexanikasi
- UIGestureRecognizerDelegate va jestlar to'qnashuvi
- Katta Touch Target yaratish: point(inside:with:) ni kengaytirish
- Amaliy kod: Interaktiv Swipe va Katta Touch Target komponenti
- Xulosa
- Manbalar
1. UIEvent va UITouch: Teginishning apparatdan ilovagacha sayohati {#touch-journey}
Foydalanuvchi ekranga barmog'ini tekkizgan soniyada nima sodir bo'ladi?
[ Barmoq ekranga tegdi ]
│
▼
[ Sensorli ekran digitizeri ] (Elektr sig'im o'zgaradi)
│
▼
[ IOHIDFamily (Kernel) ] (Apparat drayveri xom hodisani oladi)
│
▼
[ BackBoard.framework ] (Tizim xizmati — SpringBoard'ga uzatadi)
│
▼
[ UIApplication Event Queue ] (Ilovaning asosiy RunLoop portiga keladi)
│
▼
[ UIWindow.sendEvent(_:) ] (Hit-Testing orqali First Responder aniqlanadi)
- **
UITouch:** Ekrandagi bitta barmoqning aniq holati (koordinatasi, kuchi/force, burchagi/altitude va fazasi). - **
UIEvent:** Bir vaqtning o'zida yuz bergan barcha teginishlarni o'z ichiga oluvchi hodisa konteyneri (type: .touches).
2. UIResponder metodlari: touchesBegan, Moved, Ended, Cancelled {#responder-touches}
Har bir UIView bu UIResponder hisoblanadi. Teginish kelganda quyidagi 4 ta asosiy metod chaqiriladi:
import UIKit
class TouchTrackingView: UIView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
}
}
touchesCancelled qachon yuz beradi? (Juda muhim!)
- Teginish davom etayotgan paytda telefon qo'ng'irog'i (System Alert) kelsa;
- Ilova fondan chiqib ketsa;
- Eng ko'p tarqalgani: **
UIGestureRecognizer(masalan,UIScrollViewning pan jesti) teginishni o'z nazoratiga olsa!**
3. UIGestureRecognizer arxitekturasi va State Machine {#state-machine}
Oddiy touchesBegan da ishlash qiyin (masalan, 2 marta tez bosishni yoki 45 gradus chimchilashni qo'lda hisoblash murakkab). Shuning uchun Apple **UIGestureRecognizer** sinfini yaratgan.
Gesture Recognizer'lar State Machine (Holat Mashinasi) tamoyilida ishlaydi va ular 2 toifaga bo'linadi:
1. DISCRETE GESTURES (Bir martalik: UITapGestureRecognizer, UISwipeGestureRecognizer):
[ .possible ] ─────── (Muvaffaqiyat) ──────► [ .recognized ]
│
└────────────── (Shart bajarilmadi) ─► [ .failed ]
2. CONTINUOUS GESTURES (Davomiy: UIPanGestureRecognizer, UIPinchGestureRecognizer, UIRotationGestureRecognizer):
[ .possible ] ───► [ .began ] ───► [ .changed ] ───► [ .ended ]
│ │
│ └─── (Uzildi) ──► [ .cancelled ]
└───────────► [ .failed ]
4. cancelsTouchesInView va delaysTouchesBegan mexanikasi {#cancels-touches}
Bu har bir Senior iOS dasturchi bilishi shart bo'lgan eng nozik mexanizmdir!
UIGestureRecognizer view'ga qo'shilganda ikkita xususiyat standart true bo'ladi:
1. cancelsTouchesInView = true (Standart holat):
- Barmoq tekkanda, avval view'ga
touchesBeganyuboriladi (masalan, tugma bosilgan ko'rinishga kiradi). - Lekin Gesture Recognizer barmoq harakatini o'z jesti deb tanisa (
.recognizedyoki.began), u darhol view'ga **touchesCancelled** xabarini yuboradi! - Natijada, tugmaning
touchUpInsidehodisasi bekor qilinadi. - Yechim: Agar view ichidagi tugmalar ham ishlashi kerak bo'lsa,
gesture.cancelsTouchesInView = falseqilinadi.
2. delaysTouchesBegan = true (Standart holat):
- Tizim avvaliga view'ga
touchesBeganni yubormay kutib turadi. Faqat Gesture Recognizer bu mening jestim emas deb.failedbo'lsagina, view'ga kechiktirilgantouchesBeganuzatiladi.
5. UIGestureRecognizerDelegate va jestlar to'qnashuvi {#delegates}
Ko'pincha ekranda ikkita jest to'qnashib qoladi. Masalan: Gorizontal UIScrollView ichidagi kartani chapga/o'ngga surish (Swipe to delete) va butun ekranning UIPanGestureRecognizeri.
Buni hal qilish uchun **UIGestureRecognizerDelegate** ishlatiladi:
import UIKit
final class GestureCoordinator: NSObject, UIGestureRecognizerDelegate {
// 1. Ikkala jest bir vaqtda baravar ishlashi uchun:
func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
) -> Bool {
// Masalan, ham pan, ham pinch (masshtablash) bir vaqtda ishlasin:
return true
}
func setupDependencies(singleTap: UITapGestureRecognizer, doubleTap: UITapGestureRecognizer) {
// 2. Bitta jest boshqasi yiqilgandagina (failed bo'lgandagina) boshlansin:
// Masalan: Bir marta bosish (Single Tap) ikki marta bosish (Double Tap) failed bo'lgandagina ishlasin:
singleTap.require(toFail: doubleTap)
}
}
6. Katta Touch Target yaratish: point(inside:with:) ni kengaytirish {#touch-target}
Apple'ning Human Interface Guidelines (HIG) talabiga ko'ra, har qanday bosiladigan tugmaning minimal o'lchami kamida 44x44 pt bo'lishi shart.
Ammo dizayner sizga eni 16x16 pt bo'lgan kichkina "X" (Yopish) ikonkasi chizib berdi. Agar uni 16x16 qilsangiz, foydalanuvchi barmoq bilan ura olmay asabiylashadi. Agar view'ni 44x44 qilsangiz, dizayn buziladi.
Professional yechim: View'ning ko'rinishi 16x16 pt qoladi, lekin uning teginishni qabul qilish chegarasini (point(inside:with:)) dasturiy ravishda 44x44 pt ga kengaytiramiz!
┌ - - - - - - - - - - - - - - - ┐ <-- Teginish maydoni: 44x44 pt
┆ ┆ (point(inside:with:) = true)
┆ ┌───────────┐ ┆
┆ │ [ X ] │ ┆ <-- Vizual view: 16x16 pt
┆ │ (16x16 pt)│ ┆ (bounds)
┆ └───────────┘ ┆
┆ ┆
└ - - - - - - - - - - - - - - - ┘
7. Amaliy kod: Interaktiv Swipe va Katta Touch Target komponenti {#example}
Quyida Swift 6 qoidalariga mos, point(inside:with:) kengaytirilgan va jestlar to'qnashuvi hal qilingan tayyor komponent:
import UIKit
// 1. Kichik o'lchamli, lekin 44x44 pt teginish maydoniga ega Custom Button:
public final class ExpandedTouchAreaButton: UIButton {
public var minimumHitArea: CGSize = CGSize(width: 44, height: 44)
public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
// Agar o'lcham 44 dan kichik bo'lsa, chegaralarni tashqariga kengaytiramiz:
let widthDelta = max(0, minimumHitArea.width - bounds.width)
let heightDelta = max(0, minimumHitArea.height - bounds.height)
let expandedBounds = bounds.insetBy(dx: -widthDelta / 2, dy: -heightDelta / 2)
return expandedBounds.contains(point)
}
}
// 2. Bir vaqtda bir nechta jestni qabul qiluvchi interaktiv karta:
public final class InteractiveGestureCard: UIView, UIGestureRecognizerDelegate {
private let closeButton: ExpandedTouchAreaButton = {
let btn = ExpandedTouchAreaButton(type: .system)
btn.translatesAutoresizingMaskIntoConstraints = false
btn.setTitle("✕", for: .normal)
btn.tintColor = .secondaryLabel
return btn
}()
public override init(frame: CGRect) {
super.init(frame: frame)
setupGestures()
}
public required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupGestures() {
backgroundColor = .secondarySystemBackground
layer.cornerRadius = 16
addSubview(closeButton)
NSLayoutConstraint.activate([
closeButton.topAnchor.constraint(equalTo: topAnchor, constant: 12),
closeButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
closeButton.widthAnchor.constraint(equalToConstant: 24),
closeButton.heightAnchor.constraint(equalToConstant: 24)
])
// Pan gesture (surish) qo'shamiz:
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
panGesture.delegate = self
// Muhim: Tugma bosilishini bekor qilmasligi uchun:
panGesture.cancelsTouchesInView = false
addGestureRecognizer(panGesture)
}
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: self)
if gesture.state == .changed {
transform = CGAffineTransform(translationX: translation.x, y: translation.y)
} else if gesture.state == .ended || gesture.state == .cancelled {
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5) {
self.transform = .identity
}
}
}
// UIGestureRecognizerDelegate:
public func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
) -> Bool {
// Boshqa ichki jestlar bilan birga ishlashiga ruxsat beramiz:
return true
}
}
8. Xulosa {#xulosa}
- Teginish apparatdan
IOHIDFamilyvaBackBoardorqali ilovaning asosiy RunLoop'igaUIEventko'rinishida keladi. UIResponderdarajasidagi 4 ta metod (touchesBegan/Moved/Ended/Cancelled) pastki qatlamni tashkil etadi.UIGestureRecognizerState Machine arxitekturasida ishlaydi (Discrete:.possible -> .recognized/.failed; Continuous:.began -> .changed -> .ended/.cancelled).cancelsTouchesInView = truebo'lsa, jest tanilgan paytda view'gatouchesCancelledjo'natilib, tugma bosilishi uzib yuboriladi.point(inside:with:)ni override qilish orqali vizual dizaynni buzmasdan, Apple HIG talabidagi 44x44 pt qulay teginish maydonini yaratish mumkin.