---
title: Hareketler
source: 'https://academia.sh/tr/kurslar/swiftui/hareketler'
course: 'SwiftUI ile Arayüz'
language: tr
updated: '2026-10-21T05:03:13+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Hareketin geçici değerini hareket durumunda, kalıcı sonucunu durumda tutmayı ayırt eder'
    - 'Sürükleme sırasındaki değişikliğin yalnız hareketi taşıyan görünümü yeniden hesaplattığını ölçer'
    - 'Hareket bitince yazılan konumun okuyan kardeşlere ebeveyn tetiğiyle ulaştığını ölçer'
    - 'Hareket durumunun ilk değerine dönüşünün ayrı bir değişiklik olarak sayıldığını gösterir'
---

# Hareketler

Sürükleme sırasındaki geçici değerin yerel, hareket bitince yazılan kalıcı konumun ebeveyn tetiğiyle yeniden hesaplamaya yol açtığının ölçümü.

Önceki konuda bir değerin hangi yoldan görünüme bağlandığına baktık; kullanıcı bu değerleri çoğunlukla bir hareketle değiştirir. Bu derste soru değişmez: **bu yeniden hesaplamayı ne tetikledi?** Bu derste bakılan tetik **yerel**. Kursun açılış dersinde altı değişiklik sekiz yeniden hesaplama üretmişti; ikisi yerel, ikisi ebeveyn, ikisi gözlem, ikisi ortam tetiğine aitti. Şimdi tek bir sürüklemenin bu dört kategoriden hangilerini kullandığını sayacağız.

Hareket (gesture), kullanıcının ekrana dokunuşunu bir değere çeviren bildirimsel bir tanımdır: dokunma, uzun basma, sürükleme ve bunların birleşimleri aynı biçimde görünüme bir değiştirici olarak eklenir. Sürüklemenin bir özelliği ölçüyü belirler. Parmak ekrandayken konum saniyede birçok kez değişir, ama parmak kalkınca bu ara değerlerin hiçbiri kalıcı olmak zorunda değildir. Çerçeve bunun için ayrı bir özellik sarmalayıcısı sunar: `@GestureState`. Hareket sürerken değer güncellenir, hareket bitince değer kendiliğinden ilk değerine döner. Kalıcı sonuç ise hareketin bitiş geri çağrısında, sıradan bir durum değerine yazılır.

Ölçüm çekirdek dosyayla yapılır; çekirdek her derste aynı dosya adıyla ve aynı içerikle gelir.

```swift
// Invalidation.swift — bir degisikligin hangi gorunumu hangi kaynak yoluyla yeniden hesaplattigini siniflandirir
enum Storage {
    case state(owner: String)
    case model(object: String)
    case environment
}

struct ViewNode {
    let name: String
    let parent: String?
    let reads: Set<String>

    init(_ name: String, parent: String?, reads: Set<String>) {
        self.name = name
        self.parent = parent
        self.reads = reads
    }
}

struct Recompute {
    let view: String
    let cause: String
}

struct ViewTree {
    let nodes: [ViewNode]
    let storage: [String: Storage]

    func ancestors(of name: String) -> [String] {
        var found: [String] = []
        var current = nodes.first { $0.name == name }?.parent
        while let next = current {
            found.append(next)
            current = nodes.first { $0.name == next }?.parent
        }
        return found
    }

    func cause(of node: ViewNode, key: String) -> String {
        guard let kind = storage[key] else { return "olcum-disi" }
        switch kind {
        case .environment:
            return "ortam"
        case .model:
            return "gozlem"
        case .state(let owner):
            if owner == node.name { return "yerel" }
            if ancestors(of: node.name).contains(owner) { return "ebeveyn" }
            return "kural-disi"
        }
    }

    func change(_ label: String, key: String) -> [Recompute] {
        let hits = nodes.filter { $0.reads.contains(key) }
            .map { Recompute(view: $0.name, cause: cause(of: $0, key: key)) }
        let skipped = nodes.filter { !$0.reads.contains(key) }.map(\.name)
        let recomputed = hits.isEmpty ? "yok" : hits.map { "\($0.view) \($0.cause)" }.joined(separator: ", ")
        let rest = skipped.isEmpty ? "yok" : skipped.joined(separator: ",")
        print("\(label): \(key) degisti | yeniden \(recomputed) | atlanan \(rest)")
        return hits
    }
}

func countCauses(_ recomputes: [Recompute]) {
    for cause in ["yerel", "ebeveyn", "gozlem", "ortam"] {
        print("\(cause): \(recomputes.filter { $0.cause == cause }.count)")
    }
}
```

## Sürüklemenin İki Değeri

Aşağıdaki parça bir kartı sürükler. Kartı çizen görünüm geçici ofseti kendi hareket durumunda tutar. Kalıcı konum ise bir üst görünümdedir ve karta bağlama ile verilir; konumu bir etiket de okur.

```swift
import SwiftUI

struct BoardScreen: View {
    @State private var position = CGSize.zero

    var body: some View {
        VStack {
            DragCard(position: $position)
            PositionLabel(position: position)
            HintText()
        }
    }
}

struct DragCard: View {
    @Binding var position: CGSize
    @GestureState private var dragOffset = CGSize.zero

    var body: some View {
        RoundedRectangle(cornerRadius: 12)
            .offset(
                x: position.width + dragOffset.width,
                y: position.height + dragOffset.height
            )
            .gesture(
                DragGesture()
                    .updating($dragOffset) { value, state, _ in
                        state = value.translation
                    }
                    .onEnded { value in
                        position.width += value.translation.width
                        position.height += value.translation.height
                    }
            )
    }
}

struct PositionLabel: View {
    let position: CGSize

    var body: some View {
        Text("\(Int(position.width)), \(Int(position.height))")
    }
}

struct HintText: View {
    var body: some View {
        Text("Kartı sürükleyin")
    }
}
```

Bu parça derlenmez ve çıktı vermez; platform çerçevesini içe aldığı için yalnız kaynak olarak durur. Ağacı buradan okuyup düz değerlerle yeniden kuracağız. `DragCard` iki değeri okur: `dragOffset` kendi hareket durumudur, `position` ise ebeveynden gelen bağlamadır. `PositionLabel` yalnız `position`'ı okur. `HintText` hiçbirini okumaz. `BoardScreen` konumun sahibidir ve gövdesinde onu bağlama olarak ve değer olarak iletir; yani gövdesi de konumu okur.

Ölçmeden önce modelin ne olduğunu söyleyelim. Model, çerçevenin bağımlılık izleme kuralını kurar, çerçevenin kendisi değildir: bir görünüm okuduğu değer değişince yeniden hesaplanır, okumadığı değer değişince atlanır. Çerçevenin gövdeyi kaç kez çağırdığı ya da ne zaman çizdiği burada ölçülmez.

Çekirdek bir şey değiştirmez; bir anahtar verildiğinde onu okuyanları bulur ve her biri için tetiği sınıflandırır. Şimdi yukarıdaki kaynak parçadaki tip adlarıyla aynı adları taşıyan ağacı kuralım ve üç değişiklik yazalım: sürükleme sürerken `dragOffset`, hareket bitince `position` ve hareket durumunun ilk değerine dönüşü.

```swift
// main.swift — surukleme sirasinda ve sonrasinda uc degisiklik
let storage: [String: Storage] = [
    "position": .state(owner: "BoardScreen"),
    "dragOffset": .state(owner: "DragCard"),
]

let screen = ViewTree(nodes: [
    ViewNode("BoardScreen", parent: nil, reads: ["position"]),
    ViewNode("DragCard", parent: "BoardScreen", reads: ["position", "dragOffset"]),
    ViewNode("PositionLabel", parent: "BoardScreen", reads: ["position"]),
    ViewNode("HintText", parent: "BoardScreen", reads: []),
], storage: storage)

let recomputes = [
    screen.change("surukleme-suruyor", key: "dragOffset"),
    screen.change("surukleme-bitti-konum", key: "position"),
    screen.change("surukleme-bitti-ofset", key: "dragOffset"),
].flatMap { $0 }

countCauses(recomputes)
```

```
surukleme-suruyor: dragOffset degisti | yeniden DragCard yerel | atlanan BoardScreen,PositionLabel,HintText
surukleme-bitti-konum: position degisti | yeniden BoardScreen yerel, DragCard ebeveyn, PositionLabel ebeveyn | atlanan HintText
surukleme-bitti-ofset: dragOffset degisti | yeniden DragCard yerel | atlanan BoardScreen,PositionLabel,HintText
yerel: 3
ebeveyn: 2
gozlem: 0
ortam: 0
```

Sürükleme sürerken tek görünüm yeniden hesaplanır: kartın kendisi, çünkü `dragOffset` onun hareket durumudur ve yalnız onun gövdesi okur. İki kardeşi ve ebeveyni atlanır. Bu, sürüklemenin ucuz olmasının nedenidir: parmak her kıpırdadığında etiketin ya da ipucu metninin gövdesi yeniden hesaplanmaz.

**EK1.** Bu ölçüm yalnız `dragOffset` değerinin sahibinin `DragCard` olduğu ağaç için geçerlidir. Hareket durumunu bir üst görünüme taşıyan bir düzenin sayıları farklı olurdu; o düzen ölçülmedi.

**EK2.** Bu ölçüm sürükleme sırasında tek bir değişikliği temsil eder. Parmağın bir saniyede kaç ara değer ürettiği ve çerçevenin bu değerleri birleştirip birleştirmediği ölçülmedi.

## Kalıcı Konum Ebeveyne Yazılır

İkinci satır farklı bir hikâye anlatır. Hareket bitince kalıcı konum `position`'a yazılır ve bu değerin sahibi `BoardScreen`'dir. Sahip kendisi yeniden hesaplanır, yani tetik yereldir. Değeri bağlama ile okuyan `DragCard` ve değeri parametre olarak alan `PositionLabel` ise sahibin altındadır, yani onlar için tetik **ebeveyn**dir: değişikliği kendileri yapmadılar, bir üst görünümün değeri değişti. `HintText` yine atlanır, çünkü hiçbir şey okumaz.

Üçüncü satır, hareket durumunun ilk değerine dönüşüdür. Çerçeve bu dönüşü bizim adımıza yapar, ama model yapmaz: dönüş, modelde yazar tarafından ayrı bir değişiklik olarak eklendi. Kart bu değişiklikte bir kez daha yeniden hesaplanır ve tetik yine yereldir. Bu yüzden son sayım yerel tetik için üç, ebeveyn tetiği için iki yeniden hesaplama verir: üç yerelin ikisi kartındır, biri ekranındır; iki ebeveynin ikisi de konum değişikliğinden gelir. Gözlem ve ortam sayıları sıfırdır, çünkü bir sürükleme bu iki kanalı hiç kullanmaz. Açılış dersindeki 2/2/2/2 dağılımı ile bu dağılımın farkı, değişikliğin türünden gelir: tek bir etkileşim çoğunlukla tek bir ya da iki kanalın içinde kalır.

Bu ayrımın kullanım sonucu şudur. Parmakla birlikte hareket eden değer hareket durumunda tutulursa, o değeri okumayan kardeşler sürükleme boyunca etkilenmez. Aynı değer doğrudan `position` üzerinde güncellenseydi, her ara değer etiketi de yeniden hesaplatırdı. İki değeri ayırmak, çerçevenin sunduğu araca uyan bir tasarımdır ve ölçüm bunu sayıyla gösterir.

**EK3.** Bu ölçüm, `DragCard`, `PositionLabel` ve `HintText` görünümlerinin `BoardScreen`'in doğrudan çocukları olduğu ağaç için geçerlidir. Okuyanların daha derin bir ağaca yerleştiği düzen ölçülmedi.

**EK4.** Bu ölçüm hareketin bitişinde `position` değerinin bir kez yazıldığını varsayar. Bitiş geri çağrısının hangi anda ve hangi iş parçacığında koştuğu ölçülmedi.

## Dokunma ve Birleşik Hareketler

Sürükleme bu ailenin yalnız bir üyesidir. Dokunma, uzun basma ve sürükleme ayrı hareket tanımlarıdır; bir görünüme tek tek eklenebilir ya da birleştirilebilir. Birleştirmenin üç biçimi vardır: biri aynı anda çalışan hareketleri, biri sırayla çalışan hareketleri, öteki ise yalnız birinin başarılı olduğu ve ilkine öncelik veren hareketleri tanımlar. Aşağıdaki parça, önce uzun basılmasını sonra sürüklenmesini isteyen bir sıra kurar ve ayrıca dokunmayı bir durum değerine bağlar.

```swift
import SwiftUI

struct CardRow: View {
    @State private var isPinned = false

    var body: some View {
        Text("Kart")
            .onTapGesture {
                isPinned.toggle()
            }
            .gesture(
                LongPressGesture()
                    .sequenced(before: DragGesture())
            )
    }
}
```

Bu parçada yeni bir tetik kategorisi yoktur. Dokunma kapanışı sıradan bir durum değerine yazar; değerin sahibi `CardRow` olduğu için değişiklik yereldir ve `isPinned`'i okuyan başka bir görünüm yoksa yalnız satırın kendisi yeniden hesaplanır. Hareketin kendisi bir tetik değildir: tetik, hareketin sonunda yazılan ya da hareket sırasında güncellenen değerdir. Bu ayrım dersin ana fikridir ve ölçümün neden `dragOffset` ve `position` anahtarlarıyla yapıldığını açıklar. Modelde "sürükleme" adlı bir anahtar yoktur; anahtarlar, hareketin dokunduğu saklama yerleridir.

Sıralı bir birleşimde ilk hareket başarılı olmadan ikincisi başlamaz. Bu davranış çerçevenin hareket tanıma kurallarına aittir ve bu derste ölçülmez; model yalnız, birleşimin sonunda hangi değerin yazıldığını sorar.

## Sınırlayıcı Ölçüm

Modelin neyi ölçemediğini ayrı bir blokta gösterelim. Hareketin sunduğu her bilgi bir saklama yerine bağlanmaz: sürükleme hızı gibi bir değer yalnız geri çağrının içinde okunur ve hiçbir durumda tutulmaz. Modelde böyle bir değerin saklama kaydı yoktur. Okuyan bir görünüm tanımlayıp kaydı vermezsek çekirdek, tetiği sınıflandıramaz.

```swift
// main.swift — saklama kaydi olmayan bir anahtar
let storage: [String: Storage] = [
    "position": .state(owner: "BoardScreen"),
]

let screen = ViewTree(nodes: [
    ViewNode("BoardScreen", parent: nil, reads: ["position"]),
    ViewNode("DragCard", parent: "BoardScreen", reads: ["position", "velocity"]),
], storage: storage)

let recomputes = screen.change("hiz-okundu", key: "velocity")
print(recomputes.count)
```

```
hiz-okundu: velocity degisti | yeniden DragCard olcum-disi | atlanan BoardScreen
1
```

Çekirdek `olcum-disi` yazar ve bu, bir tetik kategorisi değildir; modelin "bu değeri nereden okuyorsun, bilmiyorum" deme biçimidir. Dersin sayıları yalnız dört kategoriden birine düşen değişiklikler için geçerlidir. Hareket durumunun ilk değerine ne zaman döndüğü de aynı sınıftadır: model dönüşü yazar eklediği için sayar, çerçevenin zamanlamasını iddia etmez. Dönüşün bitiş geri çağrısından önce mi sonra mı olduğu ölçülmedi.

## Özet

- Hareket, kullanıcının ekrana dokunuşunu bir değere çeviren bildirimsel bir tanımdır; geçici değer hareket durumunda, kalıcı sonuç durumda tutulur.
- Sürükleme sürerken değişen hareket durumu yalnız onu okuyan görünümü yeniden hesaplatır; kardeşler atlanır.
- Hareket bitince yazılan konum sahibine yerel, onu bağlama ya da parametreyle okuyan çocuklarına ebeveyn tetiğiyle ulaşır.
- Hareket durumunun ilk değerine dönüşü modelde ayrı bir yerel değişikliktir; çerçevenin zamanlaması ölçülmez.
- Bu dersin tetiği: **yerel**.

## Sonraki Adım

Bir sürüklemenin sonunda konum değişir ve görünüm yeni yerine geçer; bu geçişin ani mi yoksa yumuşak mı olacağı ayrı bir sorudur. Sonraki derste animasyonla sarılan değişikliğin, sarılmamış aynı değişiklikten tetik açısından ayrılıp ayrılmadığını ölçeceğiz.
