İçeriğe geç / Skip to content / Zum Inhalt
Ahmet Balaman LogoAhmet Balaman

SwiftUI ile İlk Uygulama: Adım Adım Başlangıç Rehberi

Ahmet Balaman

6 dk okuma

SwiftSwiftUIiOSXcodeStateList
SwiftUI ile İlk Uygulama: Adım Adım Başlangıç Rehberi

SwiftUI, Apple'ın arayüzü kodla tarif ettiğiniz arayüz kütüphanesidir: ekranda ne görmek istediğinizi yazarsınız, veri değiştiğinde ekranı güncellemek SwiftUI'ın işidir. Bu yazıda boş bir Xcode projesinden başlayıp çalışan bir yapılacaklar listesi uygulamasına kadar gidiyoruz. Hedef ezber değil; App, View, @State, List ve TextField parçalarının birbirine nasıl bağlandığını görmek.

Swift sözdizimine tamamen yabancıysanız önce Optionals ve guard let yazısına göz atın; buradaki kodda guard ve opsiyonel kavramı geçiyor.

Xcode'da Projeyi Oluşturmak

SwiftUI ile iOS uygulaması geliştirmek için bir Mac ve App Store'dan ücretsiz indirilen Xcode gerekiyor. Simülatörde çalıştırmak için ücretli geliştirici hesabına ihtiyaç yok.

  1. Xcode'u açın, Create New Project seçin.
  2. Üstten iOS, şablon olarak App seçin.
  3. Product Name alanına TodoList yazın. Interface SwiftUI, Language Swift olsun.
  4. Depolama (Storage) seçeneğini None bırakın; ilk uygulamada veritabanına girmeyeceğiz.
  5. Klasörü seçip projeyi oluşturun.

Xcode iki Swift dosyası üretir: TodoListApp.swift ve ContentView.swift. Uygulamanın tamamı şimdilik bu ikisinden ibaret.

App, Scene ve View: Üç Katman

TodoListApp.swift dosyası uygulamanın giriş noktasıdır:

import SwiftUI

@main
struct TodoListApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Burada üç kavram var:

  • App: Uygulamanın kendisi. @main işareti "program buradan başlar" demektir; projede tek bir tane olur.
  • Scene: Uygulamanın bir penceresi. WindowGroup iPhone'da tek tam ekran pencere anlamına gelir; iPad ve Mac'te aynı kod birden fazla pencere açabilir.
  • View: Ekranda gördüğünüz her şey. ContentView ilk ekranımız.

Flutter'dan geliyorsanız eşleştirme kolay: App kabaca runApp ile MaterialApp ikilisine, View ise widget'a karşılık gelir. Her View bir struct'tır ve tek zorunluluğu bir body döndürmesidir. Neden class değil de struct olduğunu struct ve class farkı yazısında ayrıca anlattım.

VStack ve HStack ile Yerleşim

SwiftUI'da yerleşim iç içe kutularla kurulur. VStack çocuklarını alt alta, HStack yan yana dizer; ZStack ise üst üste bindirir.

struct HelloView: View {
    var body: some View {
        VStack(spacing: 12) {
            Text("Merhaba SwiftUI")
                .font(.title)
            HStack {
                Image(systemName: "star.fill")
                Text("İlk ekranım")
            }
        }
        .padding()
    }
}

.font(.title) ve .padding() gibi çağrılara modifier denir. Her modifier, önündeki view'ı sarıp yeni bir view döndürür; bu yüzden sıra önemlidir. .padding() sonrası .background(...) yazarsanız boşluk da boyanır, tersinde boyanmaz.

@State: Ekranı Veriye Bağlamak

View'lar struct olduğu için body içinden kendi özelliklerini değiştiremezler. Değişebilen veriyi SwiftUI'a emanet etmek gerekir; bunun en basit yolu @State'tir.

struct CounterView: View {
    @State private var count = 0

    var body: some View {
        VStack(spacing: 16) {
            Text("Sayaç: \(count)")
                .font(.largeTitle)
            Button("Artır") {
                count += 1
            }
        }
    }
}

count değiştiğinde SwiftUI body'yi yeniden hesaplar ve ekranda sadece değişen kısmı günceller. setState çağırmak ya da etiketi elle güncellemek yok. @State her zaman private tutulur, çünkü o veri yalnızca o view'a aittir.

Yapılacaklar Listesini Kurmak

Önce modeli yazalım. ContentView.swift dosyasının üstüne ekleyebilirsiniz:

struct TodoItem: Identifiable {
    let id = UUID()
    var title: String
    var isDone = false
}

Identifiable, SwiftUI'ın listedeki satırları birbirinden ayırabilmesi için gereklidir. Satır silindiğinde ya da sırası değiştiğinde hangi satırın hangisi olduğunu id üzerinden takip eder.

Şimdi ekranın kendisi:

struct ContentView: View {
    @State private var items: [TodoItem] = [
        TodoItem(title: "Xcode'u kur"),
        TodoItem(title: "İlk SwiftUI ekranını yaz")
    ]
    @State private var newTitle = ""

    private var trimmedTitle: String {
        newTitle.trimmingCharacters(in: .whitespaces)
    }

    var body: some View {
        NavigationStack {
            VStack(spacing: 0) {
                HStack {
                    TextField("Yeni görev", text: $newTitle)
                        .textFieldStyle(.roundedBorder)
                        .onSubmit(addItem)

                    Button("Ekle", action: addItem)
                        .disabled(trimmedTitle.isEmpty)
                }
                .padding()

                List {
                    ForEach($items) { $item in
                        TodoRow(item: $item)
                    }
                    .onDelete { offsets in
                        items.remove(atOffsets: offsets)
                    }
                }
            }
            .navigationTitle("Yapılacaklar")
        }
    }

    private func addItem() {
        guard !trimmedTitle.isEmpty else { return }
        items.append(TodoItem(title: trimmedTitle))
        newTitle = ""
    }
}

Dikkat edilecek üç nokta var.

$newTitle: TextField metni sadece okumaz, kullanıcı yazdıkça değiştirir. Bu yüzden değerin kendisini değil, ona giden iki yönlü bağlantıyı ister. Başına $ koymak "bu state'in Binding'ini ver" demektir.

ForEach($items) { $item in … }: Diziyi binding olarak dolaşıyoruz ki her satır kendi elemanını değiştirebilsin. Sadece okuyacak olsaydık ForEach(items) { item in … } yeterdi.

.onDelete: Satırı sola kaydırınca çıkan silme davranışını tek satırla kazandırır. List kaydırma, ayraç çizgileri ve hücre geri dönüşümünü de kendisi halleder.

Satırı ayrı bir view'a ayırdık:

struct TodoRow: View {
    @Binding var item: TodoItem

    var body: some View {
        Button {
            item.isDone.toggle()
        } label: {
            HStack {
                Image(systemName: item.isDone ? "checkmark.circle.fill" : "circle")
                    .foregroundStyle(item.isDone ? .green : .secondary)
                Text(item.title)
                    .strikethrough(item.isDone)
                    .foregroundStyle(item.isDone ? .secondary : .primary)
                Spacer()
            }
        }
        .buttonStyle(.plain)
    }
}

@Binding, verinin sahibi olmayan ama onu değiştirebilen view'lar içindir. Veri ContentView'da yaşar, TodoRow sadece ödünç alır. Bu sahiplik ayrımı uygulama büyüdükçe önem kazanır; devamını @State, @Binding ve @Observable ile veri akışı yazısında bulabilirsiniz.

Önizleme: Çalıştırmadan Görmek

Dosyanın altına önizleme ekleyin:

#Preview {
    ContentView()
}

#Preview("Tamamlanmış satır") {
    TodoRow(item: .constant(TodoItem(title: "Örnek görev", isDone: true)))
}

Xcode'un sağındaki canvas kodu kaydettikçe yenilenir. Canvas görünmüyorsa Editor > Canvas menüsünden açın. İkinci önizlemedeki .constant(...), binding bekleyen bir view'a sabit değer vermenin yoludur. Bir view'ı farklı durumlarıyla ayrı ayrı önizlemek, simülatörde o duruma tıklayarak ulaşmaya çalışmaktan çok daha hızlıdır.

Simülatörde ve Gerçek Cihazda Çalıştırmak

Xcode penceresinin üstündeki cihaz menüsünden bir iPhone simülatörü seçip Cmd + R ile çalıştırın. İlk derleme biraz sürer, sonrakiler hızlıdır.

Kendi telefonunuzda denemek için:

  1. iPhone'u kabloyla bağlayın ve "Bu bilgisayara güven" sorusunu onaylayın.
  2. Proje ayarlarında Signing & Capabilities sekmesinde Team olarak Apple hesabınızı seçin. Ücretsiz hesap kendi cihazınızda test için yeterlidir.
  3. iPhone'da Ayarlar > Gizlilik ve Güvenlik > Geliştirici Modu'nu açın.
  4. Cihaz menüsünden telefonunuzu seçip çalıştırın.

Uygulamayı başkalarına dağıtmak için ücretli Apple Developer Program üyeliği gerekir; o kısmı App Store'da uygulama yayınlama rehberinde adım adım anlattım.

Ne Zaman Kullanılır, Ne Zaman Kullanılmaz?

Yeni başlayan biri için SwiftUI doğru başlangıç noktasıdır: az kodla sonuç alırsınız ve Apple'ın yeni API'leri önce SwiftUI için çıkıyor. Yeni bir projede varsayılan tercih SwiftUI olmalı.

Alternatifi UIKit'tir. Eski bir kod tabanında çalışacaksanız ya da çok ince ayarlı özel bir bileşen gerekiyorsa UIKit bilgisi işe yarar. İkisi birbirini dışlamaz; bir UIKit bileşenini UIViewRepresentable ile SwiftUI içine alabilirsiniz.

Aynı uygulamayı hem iOS hem Android'e tek kod tabanıyla çıkarmak istiyorsanız SwiftUI yanlış araçtır, çünkü yalnızca Apple platformlarında çalışır. O durumda Flutter gibi çapraz platform bir çözüm daha mantıklı; seçenekleri Flutter mı React Native mi yazısında karşılaştırdım.

Sık Yapılan Hatalar

1. @State yazmadan değişkeni değiştirmeye çalışmak

struct BadCounter: View {
    var count = 0
    var body: some View {
        Button("Artır") { count += 1 }  // derlenmez
    }
}

Belirti: Left side of mutating operator isn't mutable: 'self' is immutable benzeri bir derleme hatası. Çözüm: değişkeni @State private var count = 0 olarak tanımlamak.

2. TextField'a $ olmadan değer vermek

Belirti: Cannot convert value 'text' of type 'String' to expected type 'Binding<String>' hatası. TextField("Yeni", text: newTitle) yerine text: $newTitle yazın. Kural basit: bileşen değeri değiştirecekse binding ister.

3. Modeli Identifiable yapmayı unutmak

Belirti: ForEach ya da List satırında requires that 'TodoItem' conform to 'Identifiable' hatası. Modele Identifiable ekleyip benzersiz bir id verin. id: \.self ile başlığı kimlik olarak kullanmak cazip görünür, ama aynı başlıktan iki tane olduğunda satırlar karışır, yanlış satır silinir.

4. Her şeyi tek body içine yazmak

Belirti: derleyicinin The compiler is unable to type-check this expression in reasonable time hatası ya da yüzlerce satırlık okunmaz bir body. Çözüm: TodoRow örneğindeki gibi parçaları küçük view'lara bölmek. Küçük view'lar hem derleyiciyi rahatlatır hem ayrı ayrı önizlenebilir.

Buradan Sonra

Uygulamayı kapattığınızda liste sıfırlanıyor, çünkü veri sadece bellekte. Sıradaki doğal adımlar: veriyi kalıcı saklamak, ikinci bir ekrana NavigationLink ile geçmek ve internetten veri çekmek. Sonuncusu için async/await rehberi iyi bir devam yazısı. Konuları sırayla ve bir eğitmenle çalışmak isterseniz Swift dersleri sayfasına bakabilirsiniz.

Sık Sorulan Sorular

SwiftUI öğrenmek için önce UIKit bilmek gerekir mi?

Hayır. SwiftUI'a doğrudan başlayabilirsiniz; temel Swift sözdizimini bilmek yeterli. UIKit'e, eski bir projede çalışmanız ya da SwiftUI'ın karşılamadığı özel bir bileşen gerektiğinde bakarsınız.

SwiftUI uygulaması geliştirmek için Mac şart mı?

Evet, Xcode yalnızca macOS'ta çalışır ve iOS uygulamasını derleyip imzalamak için Xcode gerekir. Swift dilinin kendisini başka platformlarda deneyebilirsiniz ama SwiftUI arayüzü ve simülatör için Mac gerekir.

Uygulamayı kendi iPhone'umda denemek için ücretli hesap gerekir mi?

Hayır. Ücretsiz bir Apple hesabıyla Xcode üzerinden kendi cihazınıza yükleyebilirsiniz. Ücretli Apple Developer Program üyeliği, uygulamayı TestFlight ya da App Store üzerinden dağıtmak istediğinizde gerekir.

@State ile @Binding arasındaki fark nedir?

@State verinin sahibidir; değeri o view saklar. @Binding sahibi başka bir view olan veriye iki yönlü bağlantıdır. Alt view veriyi değiştirecekse @Binding alır, üst view ona $ ile bağlantıyı verir.

Yorumlar