Swift ile iOS 19’da (2025) Modern App Geliştirme: SwiftUI + Swift Concurrency + MVVM + Core Data + CloudKit Tam Rehber

Yönetici

Giriş​


2025’te SwiftUI, UIKit’yi neredeyse tamamen gölgede bırakmış durumda. Apple’ın Swift Concurrency (async/await, actors) ile getirdiği modern concurrency, CloudKit ile ücretsiz senkronizasyon ve Observation framework’ü ile MVVM çok daha temiz hale geldi.

Bu rehberde sıfırdan bir Kişisel Alışkanlık Takip Uygulaması (Habit Tracker) yapacağız:
  • Habit ekleme / silme / tamamlama
  • Günlük / haftalık ilerleme takibi
  • iCloud senkronizasyonu (CloudKit)
  • SwiftUI + MVVM + @Observable
  • Async/await ile ağ işlemleri (simüle edilmiş API + yerel Core Data)
  • Widget desteği için basit bir ipucu
Gereksinimler Xcode 17+ (iOS 19 SDK), macOS Sonoma veya Sequoia Yeni proje → App (SwiftUI + Swift)

1. Proje Yapısı (Clean MVVM)​


Kod:
HabitTracker/
├── Models/
│   └── Habit.swift
├── ViewModels/
│   └── HabitViewModel.swift
├── Views/
│   ├── ContentView.swift
│   └── HabitDetailView.swift
├── Persistence/
│   └── PersistenceController.swift
├── Services/
│   └── CloudKitService.swift
└── HabitTrackerApp.swift

2. Model (@Observable ile 2025 stili)​


Models/Habit.swift

Kod:
import Foundation
import SwiftData

@Model
@Observable
final class Habit {
var id: UUID = UUID()
var title: String
var goal: Int            // günlük hedef (ör: 1 = tamamlandı, 5 = 5 kez)
var current: Int = 0
var createdAt: Date = Date()
var completedDates: [Date] = []

init(title: String, goal: Int) {
self.title = title
self.goal = goal
}

var isCompletedToday: Bool {
let calendar = Calendar.current
return completedDates.contains { calendar.isDateInToday($0) }
}

func completeToday() {
let now = Date()
if !isCompletedToday {
completedDates.append(now)
current += 1
}
}
}

3. Core Data + SwiftData Setup (Persistence)​


SwiftData 2025’te varsayılan haline geldi, Core Data’dan çok daha basit:

Persistence/PersistenceController.swift

Kod:
import SwiftData

actor PersistenceController {
    static let shared = PersistenceController()
  
    let container: ModelContainer
  
    private init() {
        do {
            container = try ModelContainer(for: Habit.self,
                                          configurations: ModelConfiguration(isStoredInMemoryOnly: false))
        } catch {
            fatalError("SwiftData container oluşturulamadı: \(error)")
        }
    }
}

HabitTrackerApp.swift içinde:

Kod:
import SwiftUI
import SwiftData

@main
struct HabitTrackerApp: App {
let persistence = PersistenceController.shared

var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(persistence.container)
}
}
}

4. ViewModel (@Observable + async/await)​


ViewModels/HabitViewModel.swift

Kod:
import SwiftUI
import SwiftData
import CloudKit

@Observable
@MainActor
final class HabitViewModel {
var habits: [Habit] = []
var errorMessage: String?

private let modelContext: ModelContext

init(modelContext: ModelContext) {
self.modelContext = modelContext
fetchHabits()
Task { await syncWithCloudKit() }
}

func fetchHabits() {
do {
let descriptor = FetchDescriptor<Habit>(sortBy: [SortDescriptor(\.createdAt, order: .reverse)])
habits = try modelContext.fetch(descriptor)
} catch {
errorMessage = "Habit'ler çekilemedi: \(error.localizedDescription)"
}
}

func addHabit(title: String, goal: Int) {
let habit = Habit(title: title, goal: goal)
modelContext.insert(habit)
fetchHabits() // refresh
Task { await saveToCloudKit(habit) }
}

func deleteHabit(_ habit: Habit) {
modelContext.delete(habit)
fetchHabits()
Task { await deleteFromCloudKit(habit) }
}

func completeHabit(_ habit: Habit) {
habit.completeToday()
try? modelContext.save()
fetchHabits()
Task { await saveToCloudKit(habit) }
}
}

5. CloudKit Senkronizasyonu (iCloud)​

Services/CloudKitService.swift


Kod:
actor CloudKitService {
static let shared = CloudKitService()
private let container = CKContainer.default()

func saveHabit(_ habit: Habit) async {
let record = CKRecord(recordType: "Habit")
record["title"] = habit.title
record["goal"] = habit.goal
record["current"] = habit.current
record["completedDates"] = habit.completedDates.map { $0 as NSDate }
record["id"] = habit.id.uuidString

do {
_ = try await container.privateCloudDatabase.save(record)
} catch {
print("CloudKit save hatası: \(error)")
}
}

func fetchHabitsFromCloud() async -> [Habit] {
// Gerçek projede subscription + change notification eklenir
// Bu örnekte basit fetch
return []
}

// delete ve update için benzer fonksiyonlar...
}

ViewModel’de saveToCloudKit ve syncWithCloudKit çağrılarını ekle.

6. Ana Arayüz (SwiftUI)​

Views/ContentView.swift

Kod:
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@State private var viewModel: HabitViewModel?
@State private var showingAddSheet = false

var body: some View {
NavigationStack {
if let vm = viewModel {
List {
ForEach(vm.habits) { habit in
HStack {
VStack(alignment: .leading) {
Text(habit.title)
.font(.headline)
Text("Hedef: \(habit.goal) - Bugün: \(habit.current)")
.font(.subheadline)
}
Spacer()
Button(action: { vm.completeHabit(habit) }) {
Image(systemName: habit.isCompletedToday ? "checkmark.circle.fill" : "circle")
.foregroundStyle(habit.isCompletedToday ? .green : .gray)
.font(.title2)
}
.buttonStyle(.plain)
}
}
.onDelete { indexSet in
for index in indexSet {
vm.deleteHabit(vm.habits[index])
}
}
}
.navigationTitle("Alışkanlık Takip")
.toolbar {
Button("Ekle") { showingAddSheet = true }
}
.sheet(isPresented: $showingAddSheet) {
AddHabitView(viewModel: vm)
}
} else {
ProgressView()
.task {
viewModel = HabitViewModel(modelContext: modelContext)
}
}
}
}
}

AddHabitView.swift (basit form):

Kod:
struct AddHabitView: View {
@Environment(\.dismiss) private var dismiss
let viewModel: HabitViewModel
@State private var title = ""
@State private var goal = 1

var body: some View {
NavigationStack {
Form {
TextField("Alışkanlık Adı", text: $title)
Stepper("Günlük Hedef: \(goal)", value: $goal, in: 1...10)
}
.navigationTitle("Yeni Alışkanlık")
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Kaydet") {
if !title.isEmpty {
viewModel.addHabit(title: title, goal: goal)
dismiss()
}
}
}
}
}
}
}

7. İleri Seviye İpuçları (2025)​

  • Widget için App Intents + TimelineProvider kullan
  • Observation ile @Bindable ve @ObservationIgnored
  • Swift 6 concurrency checking’ini etkinleştir (strict mode)
  • Test için XCTest + @MainActor preview
 
Üst