# UIKit 1: View ierarxiyasi va koordinatalar — frame, bounds, hitTest va responder chain

> **Holat:** 2026-yil sentabr. iOS interfeysining poydevori UIView va CALayer ierarxiyasiga tayanadi. Ko'plab dasturchilar `frame` va `bounds` farqini yoki teginish (touch) hodisalari qanday tarqalishini yuzaki tushunadi. Ushbu darsda iOS 26.5 simulyatorida real o'lchangan faktlar asosida koordinatalar konvertatsiyasi, `bounds.origin` siljishi, `hitTest` ning 0.01 alpha qoidasi va Responder Chain arxitekturasi tahlil qilinadi.

## Mundarija

1. [UIView va CALayer: arxitektura va mas'uliyat taqsimoti](#uiview-calayer)
2. [Frame vs Bounds vs Center vs Transform](#frame-bounds)
3. [Transform ostidagi o'lchovlar (90 gradus burilish)](#transform)
4. [Bounds.origin siljishi: UIScrollView qanday ishlaydi?](#bounds-origin)
5. [Koordinatalar konvertatsiyasi (convert:to/from)](#coordinates)
6. [Hit-Testing va point(inside:with:) qoidalari](#hit-test)
7. [Tashqariga chiquvchi subview va clipsToBounds tuzog'i](#clip-trap)
8. [Responder Chain: hodisaning UIWindow va sahnagacha sayohati](#responder-chain)
9. [Xulosa](#xulosa)
10. [Manbalar](#manbalar)

---

## 1. UIView va CALayer: arxitektura va mas'uliyat taqsimoti {#uiview-calayer}

Har bir `UIView` orqasida bitta CoreAnimation qatlami — **`CALayer`** turadi. Ular orasidagi munosabat:

- **`UIView` (UIResponder vorisi):** Foydalanuvchi teginishlarini (touch events), imo-ishoralarni (gestures) ushlaydi, accessibility va Auto Layout bilan ishlaydi.
- **`CALayer` (NSObject vorisi):** Bevosita GPU render qilish, burchaklarni yumaloqlash (`cornerRadius`), soya (`shadow`), chegaralar (`border`) va apparat darajasidagi animatsiyalar uchun javobgar.

O'lchangan tizimli faktlar:
- `view.layer.delegate === view`: **`true`** (har bir view o'z layer'ining rasmiy delegati hisoblanadi).
- `view.alpha = 0.5` o'rnatilganda, avtomatik ravishda `layer.opacity = 0.5` ga aylanadi.
- Standart holatda `view.frame == layer.frame` tengligi saqlanadi.

---

## 2. Frame vs Bounds vs Center vs Transform {#frame-bounds}

Bu to'rt xususiyat view geometriyasini to'liq belgilaydi:

| Xususiyat | Qaysi koordinata tizimida? | Vazifasi |
| :--- | :--- | :--- |
| **`frame`** | **Superview (ota view)** koordinatalarida | View'ning ota view ichidagi joylashuvi va uni to'liq o'rab turuvchi to'g'ri to'rtburchak (bounding box). |
| **`bounds`** | **Mahalliy (o'zining)** koordinatalarida | View'ning o'z ichki maydoni. Odatda `origin = (0, 0)`, `size = (width, height)`. |
| **`center`** | **Superview (ota view)** koordinatalarida | View'ning ota view ichidagi markaziy nuqtasi (`frame.origin + frame.size / 2`). |
| **`transform`** | **Affinely (CGAffineTransform)** | 2D masshtablash, burish (rotation) va siljitish matritsasi. |

> **Oltin qoida:** Agar view'ga `transform` qo'llanilgan bo'lsa (`transform != .identity`), uning **`frame` xususiyatiga to'g'ridan-to'g'ri qiymat berish yoki unga ishonish xavflidir!** Bunday holatda faqat `bounds` va `center` bilan ishlash kerak.

---

## 3. Transform ostidagi o'lchovlar (90 gradus burilish) {#transform}

iOS 26.5 simulyatorida $100 \times 50$ o'lchamli view 90 gradusga burilganda o'lchangan haqiqiy natija:

```swift
import UIKit

@MainActor
func testTransform() {
  let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
  view.transform = CGAffineTransform(rotationAngle: .pi / 2)

  // O'lchangan natija:
  // view.bounds = (0.0, 0.0, 100.0, 50.0)
  // view.frame  = (25.0, -25.0, 50.0, 100.0)
}
```

E'tibor bering:
- View'ning o'z ichki `bounds` o'lchami o'zgarmasdan $100 \times 50$ ligicha qoldi.
- Superview ko'radigan tashqi `frame` esa burilish natijasida **$50 \times 100$** ga aylandi va markazni saqlab qolish uchun `origin` manfiy $(-25.0)$ ga surildi!

![UIView Geometriyasi: Frame vs Bounds va Transform](/lessons/images/uikit/uikit_frame_vs_bounds.svg)

---

## 4. Bounds.origin siljishi: UIScrollView qanday ishlaydi? {#bounds-origin}

Ko'pchilik dasturchilar `bounds.origin` doimo `(0, 0)` bo'ladi deb o'ylashadi. Biroq `bounds.origin` o'zgartirilsa, view'ning o'z ichki chizish kamerasi siljiydi!

Haqiqiy o'lchov tajribasi:

```swift
import UIKit

@MainActor
func testBoundsOriginShift() {
  let root = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
  let parent = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
  let child = UIView(frame: CGRect(x: 10, y: 50, width: 40, height: 40))

  root.addSubview(parent)
  parent.addSubview(child)

  let before = child.convert(CGPoint.zero, to: root) // (10.0, 50.0)

  // Otasining bounds.origin.y sini 20 ga suramiz
  parent.bounds.origin = CGPoint(x: 0, y: 20)

  let after = child.convert(CGPoint.zero, to: root) // (10.0, 30.0)
}
```

O'lchangan fakt:
- Bolaning `child.frame` xususiyati o'zgarmadi: hamon `(10.0, 50.0, 40.0, 40.0)`.
- Lekin ekranda (root'da) bola **20 nuqta yuqoriga surildi (`(10.0, 30.0)`)**!
- Aynan shu mexanizm orqali **`UIScrollView`** barcha bolalarining frame'ini o'zgartirmasdan, faqat o'zining `bounds.origin`ini o'zgartirish orqali millionlab elementlarni bir zumda scroll qiladi.

![UIScrollView Bounds Origin Mexanizmi](/lessons/images/uikit/uikit_uiscrollview_bounds_origin.svg)

---

## 5. Koordinatalar konvertatsiyasi (convert:to/from) {#coordinates}

Turli ierarxiyalarda joylashgan ikki view o'rtasidagi masofani hisoblashda qo'lda qo'shish-ayirish qat'iyan man etiladi:

```swift
import UIKit

@MainActor
func calculateAbsolutePosition(button: UIButton, in window: UIWindow) -> CGRect {
  // Tugmaning window ichidagi mutlaq koordinatasini topish:
  let globalFrame = button.convert(button.bounds, to: window)
  return globalFrame
}
```

---

## 6. Hit-Testing va point(inside:with:) qoidalari {#hit-test}

Foydalanuvchi ekranga tekkanda, iOS qaysi view teginishni qabul qilishini aniqlash uchun chuqurlik bo'yicha qidiruv (**Reverse Pre-order Depth-First Search**) o'tkazadi:

```text
1. view.hitTest(point, with: event) chaqiriladi.
2. Agar quyidagilardan biri ro'y bersa, tekshirish darhol to'xtatiladi va nil qaytadi:
   - view.isHidden == true
   - view.isUserInteractionEnabled == false
   - view.alpha < 0.01  <-- DIQQAT!
   - !view.point(inside: point, with: event)
3. Aks holda, ota view o'zining barcha bolalarini oxirgi qo'shilganidan boshlab (eng yuqori qatlamdan) tekshiradi.
```

### O'lchangan faktlar (iOS 26.5):
- `child.isHidden = true` bo'lsa -> bola e'tiborsiz qoldiriladi (`hit = parent`).
- `child.isUserInteractionEnabled = false` bo'lsa -> bola o'tkazib yuboriladi (`hit = parent`).
- **Alpha chegarasi:**
  - `child.alpha = 0.005` bo'lsa -> UIKit buni ko'rinmas deb hisoblaydi va teginishni bolaga bermaydi (`hit = parent`).
  - `child.alpha = 0.02` bo'lsa -> teginish bolaga uzatiladi (`hit = child`).
  - Demak, UIKit'da teginish qabul qilish uchun minimal alpha ko'rsatkichi **`0.01`** hisoblanadi!

---

## 7. Tashqariga chiquvchi subview va clipsToBounds tuzog'i {#clip-trap}

iOS dasturchilarining 80% i ushbu xatoga yo'l qo'yadi:

Faraz qilaylik, $200 \times 200$ o'lchamli ota view bor. Unda `clipsToBounds = false` qilib, ichiga $100 \times 100$ o'lchamli tugma qo'yildi va u ota chegarasidan tashqariga chiqib turibdi (`x: 220, y: 220`).

**Foydalanuvchi tashqariga chiqib turgan tugmaga bossa, nima bo'ladi?**
- Tugma vizual ko'rinib turadi.
- Lekin bosilganda **hech narsa bo'lmaydi (`hit = nil`)**!

**Nega?**
Chunki iOS avval ota view'ning `point(inside:with:)` metodini tekshiradi. Nuqta otaning `(200, 200)` chegarasidan tashqarida bo'lgani uchun ota `false` qaytaradi va UIKit uning bolalarini tekshirib ham o'tirmaydi!

### Yechim: `point(inside:with:)` ni kengaytirish

```swift
import UIKit

public final class ExpandedHitAreaView: UIView {
  public var touchAreaPadding = UIEdgeInsets(top: -20, left: -20, bottom: -20, right: -20)

  public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    let extendedBounds = bounds.inset(by: touchAreaPadding)
    return extendedBounds.contains(point)
  }
}
```

---

## 8. Responder Chain: hodisaning UIWindow va sahnagacha sayohati {#responder-chain}

`hitTest` teginilgan dastlabki ob'ektni (First Responder) topgandan so'ng, hodisa **Responder Chain** zanjiri bo'ylab yuqoriga uzatiladi.

iOS 26.5 da `UIButton` dan boshlangan real responder chain zanjiri:

```text
UIButton
   |
UIView (tugma turgan ota ko'rinish)
   |
UIViewController (ekran boshqaruvchisi)
   |
UIDropShadowView / UITransitionView (tizimli konteynerlar)
   |
UIWindow (oyna)
   |
UIWindowScene (_UIScreenBasedWindowScene)
   |
UIApplication
   |
AppDelegate
```

Agar biron bir bo'g'in hodisani qayta ishlamasa (`super.touchesBegan(...)`), hodisa `nextResponder` orqali keyingisiga o'tadi. Hech kim ushlamasa, hodisa shunchaki bekor qilinadi.

![Hit-Testing va Responder Chain Arxitekturasi](/lessons/images/uikit/uikit_hittest_responder_chain.svg)

---

## 9. Xulosa {#xulosa}

- `UIView` foydalanuvchi muloqoti (events) uchun, uning orqasidagi `CALayer` esa apparat renderi uchun javobgardir.
- `transform` qo'llanilganda `frame` o'zgaradi, lekin `bounds` o'zgarmaydi; bunday paytda faqat `bounds` va `center` bilan ishlash shart.
- `UIScrollView` o'z bolalarining joylashuvini emas, balki o'zining `bounds.origin` nuqtasini surish orqali scroll qiladi.
- `hitTest` teginishni qabul qilishi uchun view'ning `alpha` ko'rsatkichi kamida `0.01` bo'lishi shart.
- Ota view chegarasidan tashqariga chiqqan subview `clipsToBounds = false` bo'lsa ham, otaning `point(inside:with:)` kengaytirilmasa, teginishni qabul qilmaydi.
- Responder Chain hodisalarni eng quyi view'dan to UIWindow, Scene va UIApplication'gacha uzatuvchi tizimdir.

---

## Manbalar {#manbalar}

1. [View and Window Architecture — Apple Documentation](https://developer.apple.com/documentation/uikit/views_and_controls)
2. [UIView hitTest(_:with:) — Apple Documentation](https://developer.apple.com/documentation/uikit/uiview/1622469-hittest)
3. [Understanding the Responder Chain — Apple Documentation](https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/using_responders_and_the_responder_chain_to_handle_events)
4. [Core Animation Basics — Apple Documentation Archive](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html)
