# 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 `UITableViewCell` ichidagi tugma bosilmay qolishi, `UIScrollView` ichidagi swipe yoki pan nega boshqa jest bilan to'qnashishi yoki kichik tugmalarni bosish nega noqulayligini chuqur bilmaydi. Ushbu darsda `UIEvent` va `UITouch` sayohati, Gesture Recognizer State Machine, `cancelsTouchesInView` tuzog'i va `point(inside:with:)` orqali katta Touch Target yasash texnikasi tahlil qilinadi.

## Mundarija

1. [UIEvent va UITouch: Teginishning apparatdan ilovagacha sayohati](#touch-journey)
2. [UIResponder metodlari: touchesBegan, Moved, Ended, Cancelled](#responder-touches)
3. [UIGestureRecognizer arxitekturasi va State Machine](#state-machine)
4. [cancelsTouchesInView va delaysTouchesBegan mexanikasi](#cancels-touches)
5. [UIGestureRecognizerDelegate va jestlar to'qnashuvi](#delegates)
6. [Katta Touch Target yaratish: point(inside:with:) ni kengaytirish](#touch-target)
7. [Amaliy kod: Interaktiv Swipe va Katta Touch Target komponenti](#example)
8. [Xulosa](#xulosa)
9. [Manbalar](#manbalar)

---

## 1. UIEvent va UITouch: Teginishning apparatdan ilovagacha sayohati {#touch-journey}

Foydalanuvchi ekranga barmog'ini tekkizgan soniyada nima sodir bo'ladi?

```text
[ 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)
```

1. **`UITouch`:** Ekrandagi bitta barmoqning aniq holati (koordinatasi, kuchi/force, burchagi/altitude va fazasi).
2. **`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:

```swift
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, `UIScrollView`ning 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:

```text
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 `touchesBegan` yuboriladi (masalan, tugma bosilgan ko'rinishga kiradi).
* Lekin Gesture Recognizer barmoq harakatini o'z jesti deb tanisa (`.recognized` yoki `.began`), u darhol view'ga **`touchesCancelled`** xabarini yuboradi!
* Natijada, tugmaning `touchUpInside` hodisasi bekor qilinadi.
* **Yechim:** Agar view ichidagi tugmalar ham ishlashi kerak bo'lsa, `gesture.cancelsTouchesInView = false` qilinadi.

### 2. `delaysTouchesBegan = true` (Standart holat):
* Tizim avvaliga view'ga `touchesBegan`ni yubormay kutib turadi. Faqat Gesture Recognizer bu mening jestim emas deb `.failed` bo'lsagina, view'ga kechiktirilgan `touchesBegan` uzatiladi.

---

## 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 `UIPanGestureRecognizer`i.

Buni hal qilish uchun **`UIGestureRecognizerDelegate`** ishlatiladi:

```swift
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!

```text
   ┌ - - - - - - - - - - - - - - - ┐  <-- 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:

```swift
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 `IOHIDFamily` va `BackBoard` orqali ilovaning asosiy RunLoop'iga `UIEvent` ko'rinishida keladi.
- `UIResponder` darajasidagi 4 ta metod (`touchesBegan/Moved/Ended/Cancelled`) pastki qatlamni tashkil etadi.
- `UIGestureRecognizer` **State Machine** arxitekturasida ishlaydi (Discrete: `.possible -> .recognized/.failed`; Continuous: `.began -> .changed -> .ended/.cancelled`).
- `cancelsTouchesInView = true` bo'lsa, jest tanilgan paytda view'ga `touchesCancelled` jo'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.

---

## 9. Manbalar {#manbalar}

1. [Touches, Presses, and Gestures — Apple Developer Documentation](https://developer.apple.com/documentation/uikit/touches_presses_and_gestures)
2. [UIGestureRecognizer State Machine — Apple](https://developer.apple.com/documentation/uikit/uigesturerecognizer/state)
3. [Apple Human Interface Guidelines: Touch Targets](https://developer.apple.com/design/human-interface-guidelines/inputs#Touch)
