Mobile Development

Swift untuk iOS Development

Tutorial lengkap belajar Swift dari dasar hingga membuat aplikasi iOS — Swift basics, Xcode, UIKit, SwiftUI, navigation, dan data persistence dengan contoh kode praktis

1. Pengenalan Swift & iOS Development

Swift adalah bahasa pemrograman modern yang dikembangkan oleh Apple untuk membangun aplikasi di ekosistem Apple — iOS, macOS, watchOS, tvOS, dan visionOS. Swift dirancang untuk aman, cepat, dan ekspresif, menjadikannya bahasa utama untuk pengembangan aplikasi Apple.

Diluncurkan pada tahun 2014 di WWDC, Swift menggantikan Objective-C sebagai bahasa utama untuk pengembangan iOS. Swift memiliki sintaks yang bersih, type safety yang kuat, dan fitur modern seperti closures, generics, dan protocol-oriented programming.

Mengapa Belajar Swift?

Keunggulan Penjelasan
Performa TinggiSwift mendekati performa C++ berkat LLVM compiler dan optimisasi yang canggih
Type SafetySistem tipe yang kuat mencegah banyak bug saat compile time
Memory SafetyAutomatic Reference Counting (ARC) dan optional handling yang aman
Sintaks ModernKode yang lebih ringkas dan mudah dibaca dibanding Objective-C
Ekosistem AppleAkses ke seluruh framework Apple: UIKit, SwiftUI, CoreML, ARKit, dll.
Komunitas BesarDokumentasi lengkap dari Apple, komunitas aktif, banyak tutorial
Pasar yang MenguntungkanApp Store menghasilkan revenue lebih tinggi per aplikasi dibanding Play Store

Swift vs Alternatif Mobile

Aspek Swift (iOS) Kotlin (Android) Flutter (Cross-platform)
PlatformiOS, macOS, watchOSAndroid, ServeriOS, Android, Web, Desktop
BahasaSwiftKotlinDart
UI FrameworkSwiftUI, UIKitJetpack ComposeFlutter Widget
Performa🟢 Native🟢 Native🟡 Near-native
Learning Curve🟡 Sedang🟡 Sedang🟡 Sedang
Hot Reload🟡 Terbatas🟡 Terbatas🟢 Ya
Diagram: Ekosistem Apple Development
┌───────────────────────────────────────────────────────┐
│              EKOSISTEM APPLE DEVELOPMENT               │
│                                                       │
│  ┌──────────────────────────────────────────────────┐ │
│  │                  SWIFT                           │ │
│  │     (Bahasa Pemrograman Inti)                    │ │
│  └────────────────────┬─────────────────────────────┘ │
│                       │                               │
│       ┌───────────────┼───────────────┐               │
│       ▼               ▼               ▼               │
│  ┌─────────┐   ┌───────────┐   ┌───────────┐         │
│  │  iOS    │   │   macOS   │   │  watchOS  │         │
│  │         │   │           │   │           │         │
│  │ SwiftUI │   │ SwiftUI   │   │ SwiftUI   │         │
│  │ UIKit   │   │ AppKit    │   │           │         │
│  │ ARKit   │   │ Catalyst  │   │           │         │
│  │ CoreML  │   │ CoreML    │   │           │         │
│  └─────────┘   └───────────┘   └───────────┘         │
│                                                       │
│  Tools: Xcode, Instruments, TestFlight, App Store     │
│  Frameworks: Foundation, Combine, Core Data, CloudKit │
└───────────────────────────────────────────────────────┘
💡 Tips

Untuk mengikuti tutorial ini, Anda memerlukan komputer Mac dengan Xcode terinstal (tersedia gratis di Mac App Store). Swift Playground di iPad juga bisa digunakan untuk belajar dasar-dasar Swift tanpa Xcode.

2. Dasar-dasar Swift

Swift memiliki sintaks yang intuitif bagi yang sudah familiar dengan bahasa modern. Mari kita pelajari konsep-konsep dasar yang harus dikuasai sebelum membangun aplikasi iOS.

Variabel, Konstanta, dan Tipe Data

Swift — Variables & Types
// ===== Variabel dan Konstanta =====
// var → bisa diubah (mutable)
// let → tidak bisa diubah (immutable/constant)

var nama = "Budi"           // String, type inference
let umur = 25               // Int, type inference
var tinggi: Double = 175.5  // Double, explicit type
let isActive = true         // Bool

nama = "Andi"     // ✅ Bisa diubah
// umur = 30      // ❌ Error: cannot assign to 'let'

// ===== Tipe Data Dasar =====
let angka: Int = 42
let desimal: Double = 3.14
let huruf: Character = "A"
let teks: String = "Hello, World!"
let flag: Bool = true

// ===== String Operations =====
let firstName = "Budi"
let lastName = "Santoso"
let fullName = firstName + " " + lastName  // Concatenation
let greeting = "Halo, \(firstName)!"       // String interpolation
let multiLine = """
    Ini adalah string
    multi-line
    di Swift
    """

print(fullName)    // Budi Santoso
print(greeting)    // Halo, Budi!
print(teks.count)  // 13

// ===== Collections =====
// Array — urut, bisa duplikat
var buah = ["Apel", "Mangga", "Jeruk"]
buah.append("Durian")       // Tambah di akhir
buah.insert("Anggur", at: 1) // Tambah di index tertentu
buah.remove(at: 0)           // Hapus di index
print(buah.count)             // 4
print(buah[0])                // Mangga

// Dictionary — key-value pairs
var mahasiswa: [String: Any] = [
    "nama": "Sari",
    "umur": 22,
    "jurusan": "Informatika"
]
mahasiswa["ipk"] = 3.85    // Tambah entry baru
print(mahasiswa["nama"]!)  // Sari (pakai ! untuk unwrap)

// Set — unik, tidak berurutan
var angkaUnik: Set = [1, 2, 3, 2, 1]
print(angkaUnik)  // {1, 2, 3} (duplikat dihapus)

// Tuples — group beberapa nilai
let koordinat = (lat: -6.2088, lng: 106.8456)
print("Latitude: \(koordinat.lat)")
print("Longitude: \(koordinat.lng)")

Control Flow dan Functions

Swift — Control Flow & Functions
// ===== IF-ELSE =====
let nilai = 85

if nilai >= 90 {
    print("A — Excellent")
} else if nilai >= 80 {
    print("B — Good")
} else if nilai >= 70 {
    print("C — Average")
} else {
    print("D — Below Average")
}

// ===== SWITCH (lebih powerful di Swift) =====
let cuaca = "hujan"

switch cuaca {
case "cerah":
    print("Cuaca bagus! ☀️")
case "hujan":
    print("Bawa payung! 🌧️")
case "mendung":
    print("Mungkin akan hujan ☁️")
default:
    print("Cek prakiraan cuaca 📱")
}

// Switch dengan range
switch nilai {
case 90...100:
    print("A")
case 80..<90:
    print("B")
case 70..<80:
    print("C")
default:
    print("D")
}

// ===== LOOPS =====
// For-in loop
for i in 1...5 {
    print("Iterasi ke-\(i)")
}

// Loop dengan array
let warna = ["Merah", "Hijau", "Biru"]
for w in warna {
    print("Warna: \(w)")
}

// While loop
var counter = 0
while counter < 3 {
    print("Counter: \(counter)")
    counter += 1
}

// ===== FUNCTIONS =====
func sapaan(nama: String) -> String {
    return "Halo, \(nama)! Selamat datang."
}

print(sapaan(nama: "Budi"))

// Function dengan default parameter
func hitungLuas(panjang: Double, lebar: Double = 10.0) -> Double {
    return panjang * lebar
}

print(hitungLuas(panjang: 5.0))         // 50.0 (lebar default 10)
print(hitungLuas(panjang: 5.0, lebar: 3.0))  // 15.0

// Function dengan multiple return values
func statistik(angka: [Int]) -> (min: Int, max: Int, avg: Double) {
    let minVal = angka.min()!
    let maxVal = angka.max()!
    let avgVal = Double(angka.reduce(0, +)) / Double(angka.count)
    return (minVal, maxVal, avgVal)
}

let data = [10, 20, 30, 40, 50]
let stat = statistik(angka: data)
print("Min: \(stat.min), Max: \(stat.max), Avg: \(stat.avg)")

// ===== CLOSURES =====
let angkaArr = [5, 2, 8, 1, 9, 3]

// Sorting dengan closure
let sorted = angkaArr.sorted { $0 < $1 }
print(sorted)  // [1, 2, 3, 5, 8, 9]

// Map, Filter, Reduce
let doubled = angkaArr.map { $0 * 2 }
let ganjil = angkaArr.filter { $0 % 2 != 0 }
let total = angkaArr.reduce(0) { $0 + $1 }

print(doubled)  // [10, 4, 16, 2, 18, 6]
print(ganjil)   // [5, 1, 9, 3]
print(total)    // 28

Optionals dan Error Handling

Swift — Optionals
// ===== Optionals =====
// Optional = bisa ada nilai atau nil
var namaLengkap: String? = "Budi Santoso"
var emailKosong: String? = nil

// Force unwrap (berbahaya!)
// print(namaLengkap!)  // OK, tapi crash jika nil

// Optional binding (safe)
if let nama = namaLengkap {
    print("Nama: \(nama)")
} else {
    print("Nama tidak tersedia")
}

// Guard let (early exit)
func prosesEmail(_ email: String?) {
    guard let email = email else {
        print("Email tidak valid")
        return
    }
    print("Mengirim email ke: \(email)")
}

// Nil coalescing
let displayName = namaLengkap ?? "Anonymous"
print(displayName)  // Budi Santoso

// Optional chaining
struct Alamat {
    var kota: String
    var kodePos: String
}

struct Orang {
    var nama: String
    var alamat: Alamat?
}

let budi = Orang(nama: "Budi", alamat: Alamat(kota: "Jakarta", kodePos: "12345"))
let sari = Orang(nama: "Sari", alamat: nil)

print(budi.alamat?.kota ?? "Tidak diketahui")  // Jakarta
print(sari.alamat?.kota ?? "Tidak diketahui")  // Tidak diketahui

// ===== Error Handling =====
enum NetworkError: Error {
    case noConnection
    case timeout
    case serverError(code: Int)
}

func fetchData(from url: String) throws -> String {
    if url.isEmpty {
        throw NetworkError.noConnection
    }
    if url.contains("timeout") {
        throw NetworkError.timeout
    }
    return "Data dari \(url)"
}

do {
    let data = try fetchData(from: "https://api.example.com")
    print(data)
} catch NetworkError.noConnection {
    print("Tidak ada koneksi internet")
} catch NetworkError.timeout {
    print("Request timeout")
} catch {
    print("Error: \(error)")
}

3. Mengenal Xcode

Xcode adalah IDE (Integrated Development Environment) resmi dari Apple untuk mengembangkan aplikasi iOS, macOS, watchOS, dan tvOS. Xcode menyediakan semua tools yang dibutuhkan: editor kode, Interface Builder, debugger, simulator, dan App Store publishing tools.

Fitur Utama Xcode

Fitur Fungsi
Source EditorEditor kode dengan syntax highlighting, autocomplete, dan refactoring
Interface BuilderDesain UI secara visual (drag & drop) untuk UIKit
SwiftUI PreviewPreview real-time untuk SwiftUI tanpa build
iOS SimulatorMenjalankan aplikasi di simulator iPhone/iPad
InstrumentsProfiling dan performance analysis
Asset CatalogMengelola gambar, warna, dan app icons
PlaygroundsEnvironment interaktif untuk eksperimen kode Swift

Struktur Proyek iOS

File Structure
MyApp/
├── MyApp/
│   ├── MyAppApp.swift          ← Entry point aplikasi (@main)
│   ├── ContentView.swift       ← View utama
│   ├── Models/                 ← Data models
│   │   └── User.swift
│   ├── Views/                  ← UI Views
│   │   ├── HomeView.swift
│   │   ├── DetailView.swift
│   │   └── ProfileView.swift
│   ├── ViewModels/             ← View Models (MVVM)
│   │   └── UserViewModel.swift
│   ├── Services/               ← Network, API calls
│   │   └── APIService.swift
│   ├── Assets.xcassets/        ← Gambar, warna, icons
│   │   ├── AppIcon.appiconset/
│   │   └── AccentColor.colorset/
│   ├── Info.plist              ← Konfigurasi aplikasi
│   └── Localizable.strings    ← Translasi bahasa
├── MyApp.xcodeproj            ← Project file Xcode
├── MyAppTests/                ← Unit tests
└── MyAppUITests/              ← UI tests
⚠️ Persyaratan

Xcode hanya tersedia di macOS. Jika Anda menggunakan Windows/Linux, Anda bisa menggunakan Swift Playgrounds di iPad atau layanan cloud Mac seperti MacStadium atau AWS EC2 Mac instances untuk development.

4. UIKit: Interface Builder & Programmatic UI

UIKit adalah framework UI utama Apple yang sudah digunakan sejak iOS 2. UIKit menyediakan komponen UI seperti UILabel, UIButton, UITableView, dan UINavigationController. Meskipun SwiftUI semakin populer, UIKit masih sangat relevan dan digunakan di banyak aplikasi production.

UIKit Programmatic UI

Swift — UIKit Programmatic
import UIKit

class ViewController: UIViewController {
    
    // UI Elements
    let titleLabel = UILabel()
    let nameTextField = UITextField()
    let submitButton = UIButton(type: .system)
    let resultLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        setupUI()
    }
    
    func setupUI() {
        // ===== Title Label =====
        titleLabel.text = "Selamat Datang!"
        titleLabel.font = UIFont.systemFont(ofSize: 28, weight: .bold)
        titleLabel.textColor = .label
        titleLabel.textAlignment = .center
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(titleLabel)
        
        // ===== Text Field =====
        nameTextField.placeholder = "Masukkan nama Anda"
        nameTextField.borderStyle = .roundedRect
        nameTextField.font = UIFont.systemFont(ofSize: 16)
        nameTextField.autocorrectionType = .no
        nameTextField.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(nameTextField)
        
        // ===== Submit Button =====
        submitButton.setTitle("Sapa!", for: .normal)
        submitButton.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .medium)
        submitButton.backgroundColor = .systemBlue
        submitButton.setTitleColor(.white, for: .normal)
        submitButton.layer.cornerRadius = 10
        submitButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        submitButton.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(submitButton)
        
        // ===== Result Label =====
        resultLabel.text = ""
        resultLabel.font = UIFont.systemFont(ofSize: 20, weight: .medium)
        resultLabel.textColor = .systemGreen
        resultLabel.textAlignment = .center
        resultLabel.numberOfLines = 0
        resultLabel.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(resultLabel)
        
        // ===== Auto Layout Constraints =====
        NSLayoutConstraint.activate([
            titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            
            nameTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            nameTextField.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 30),
            nameTextField.widthAnchor.constraint(equalToConstant: 280),
            nameTextField.heightAnchor.constraint(equalToConstant: 44),
            
            submitButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            submitButton.topAnchor.constraint(equalTo: nameTextField.bottomAnchor, constant: 20),
            submitButton.widthAnchor.constraint(equalToConstant: 200),
            submitButton.heightAnchor.constraint(equalToConstant: 50),
            
            resultLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            resultLabel.topAnchor.constraint(equalTo: submitButton.bottomAnchor, constant: 30),
            resultLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            resultLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
        ])
    }
    
    @objc func buttonTapped() {
        guard let name = nameTextField.text, !name.isEmpty else {
            resultLabel.text = "⚠️ Masukkan nama terlebih dahulu"
            resultLabel.textColor = .systemRed
            return
        }
        
        resultLabel.text = "Halo, \(name)! 👋 Selamat belajar iOS!"
        resultLabel.textColor = .systemGreen
        nameTextField.resignFirstResponder() // Tutup keyboard
    }
}

// ===== UITableView Contoh =====
class DaftarViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    let tableView = UITableView()
    let items = ["iPhone 16", "iPhone 16 Pro", "iPad Pro", "MacBook Air", "Apple Watch"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Produk Apple"
        
        tableView.dataSource = self
        tableView.delegate = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.frame = view.bounds
        tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(tableView)
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = items[indexPath.row]
        cell.accessoryType = .disclosureIndicator
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        print("Dipilih: \(items[indexPath.row])")
    }
}

5. SwiftUI: Declarative UI

SwiftUI adalah framework UI modern dari Apple yang diperkenalkan di WWDC 2019. SwiftUI menggunakan pendekatan declarative — cukup deskripsikan apa yang ingin ditampilkan, SwiftUI akan menangani rendering-nya. SwiftUI juga mendukung preview real-time di Xcode.

Perbandingan UIKit vs SwiftUI

Aspek UIKit SwiftUI
ParadigmaImperativeDeclarative
KodeLebih banyak boilerplateLebih ringkas
PreviewBuild & RunReal-time preview
Minimum iOSiOS 2+iOS 13+
Stabilitas🟢 Sangat stabil🟡 Terus berkembang
KomponenSangat lengkapBerkembang pesat
RekomendasiProyek existing/legacyProyek baru

SwiftUI Fundamentals

Swift — SwiftUI Basics
import SwiftUI

// ===== Entry Point =====
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

// ===== Basic View =====
struct ContentView: View {
    var body: some View {
        Text("Halo, SwiftUI! 🎉")
            .font(.largeTitle)
            .fontWeight(.bold)
            .foregroundColor(.blue)
    }
}

// ===== View dengan State =====
struct CounterView: View {
    @State private var count = 0
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Hitungan: \(count)")
                .font(.system(size: 32, weight: .semibold))
            
            HStack(spacing: 20) {
                Button("Kurang (-)") {
                    count -= 1
                }
                .buttonStyle(.bordered)
                .tint(.red)
                
                Button("Reset") {
                    count = 0
                }
                .buttonStyle(.bordered)
                
                Button("Tambah (+)") {
                    count += 1
                }
                .buttonStyle(.bordered)
                .tint(.green)
            }
        }
        .padding()
    }
}

// ===== List & Data =====
struct TodoItem: Identifiable {
    let id = UUID()
    var title: String
    var isDone: Bool
}

struct TodoListView: View {
    @State private var todos = [
        TodoItem(title: "Belajar SwiftUI", isDone: false),
        TodoItem(title: "Buat proyek pertama", isDone: false),
        TodoItem(title: "Submit ke App Store", isDone: false),
    ]
    @State private var newTodo = ""
    
    var body: some View {
        NavigationView {
            VStack {
                // Input field
                HStack {
                    TextField("Tambah tugas baru...", text: $newTodo)
                        .textFieldStyle(.roundedBorder)
                    
                    Button("Tambah") {
                        guard !newTodo.isEmpty else { return }
                        todos.append(TodoItem(title: newTodo, isDone: false))
                        newTodo = ""
                    }
                    .buttonStyle(.borderedProminent)
                }
                .padding()
                
                // List
                List {
                    ForEach(todos) { todo in
                        HStack {
                            Image(systemName: todo.isDone ? "checkmark.circle.fill" : "circle")
                                .foregroundColor(todo.isDone ? .green : .gray)
                            
                            Text(todo.title)
                                .strikethrough(todo.isDone)
                        }
                        .onTapGesture {
                            if let index = todos.firstIndex(where: { $0.id == todo.id }) {
                                todos[index].isDone.toggle()
                            }
                        }
                    }
                    .onDelete { indexSet in
                        todos.remove(atOffsets: indexSet)
                    }
                }
            }
            .navigationTitle("📋 Daftar Tugas")
        }
    }
}

// ===== Form & Input =====
struct ProfilView: View {
    @State private var nama = ""
    @State private var email = ""
    @State private var umur = 18.0
    @State private var notifikasi = true
    @State private var tema = 0
    
    let temaOptions = ["Terang", "Gelap", "Otomatis"]
    
    var body: some View {
        NavigationView {
            Form {
                Section("Informasi Pribadi") {
                    TextField("Nama", text: $nama)
                    TextField("Email", text: $email)
                        .keyboardType(.emailAddress)
                    
                    VStack(alignment: .leading) {
                        Text("Umur: \(Int(umur)) tahun")
                        Slider(value: $umur, in: 17...80, step: 1)
                    }
                }
                
                Section("Pengaturan") {
                    Toggle("Notifikasi", isOn: $notifikasi)
                    
                    Picker("Tema", selection: $tema) {
                        ForEach(0..
      

Navigation adalah elemen fundamental dalam aplikasi mobile. SwiftUI menyediakan beberapa cara untuk navigasi antar halaman: NavigationStack, NavigationLink, dan sheet untuk modal presentation.

Swift — Navigation
import SwiftUI

// ===== Model =====
struct Produk: Identifiable, Hashable {
    let id = UUID()
    let nama: String
    let harga: Int
    let deskripsi: String
    let emoji: String
}

// ===== NavigationStack dengan NavigationLink =====
struct KatalogView: View {
    let produkList = [
        Produk(nama: "iPhone 16", harga: 15999000, deskripsi: "Smartphone terbaru Apple", emoji: "📱"),
        Produk(nama: "MacBook Air M3", harga: 19999000, deskripsi: "Laptop tipis dan powerful", emoji: "💻"),
        Produk(nama: "iPad Pro", harga: 14999000, deskripsi: "Tablet profesional", emoji: "📲"),
        Produk(nama: "Apple Watch", harga: 7999000, deskripsi: "Smartwatch canggih", emoji: "⌚"),
        Produk(nama: "AirPods Pro", harga: 4299000, deskripsi: "Earbuds dengan ANC", emoji: "🎧"),
    ]
    
    @State private var searchText = ""
    
    var filteredProduk: [Produk] {
        if searchText.isEmpty {
            return produkList
        }
        return produkList.filter { $0.nama.localizedCaseInsensitiveContains(searchText) }
    }
    
    var body: some View {
        NavigationStack {
            List(filteredProduk) { produk in
                NavigationLink(value: produk) {
                    HStack {
                        Text(produk.emoji)
                            .font(.system(size: 40))
                        
                        VStack(alignment: .leading, spacing: 4) {
                            Text(produk.nama)
                                .font(.headline)
                            Text("Rp \(produk.harga.formatted())")
                                .font(.subheadline)
                                .foregroundColor(.secondary)
                        }
                    }
                    .padding(.vertical, 4)
                }
            }
            .navigationTitle("🛒 Katalog")
            .searchable(text: $searchText, prompt: "Cari produk...")
            .navigationDestination(for: Produk.self) { produk in
                DetailProdukView(produk: produk)
            }
        }
    }
}

// ===== Detail View =====
struct DetailProdukView: View {
    let produk: Produk
    @State private var quantity = 1
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 20) {
                // Hero image placeholder
                Text(produk.emoji)
                    .font(.system(size: 100))
                    .frame(maxWidth: .infinity)
                    .padding(.vertical, 40)
                    .background(Color(.systemGray6))
                    .cornerRadius(16)
                
                VStack(alignment: .leading, spacing: 8) {
                    Text(produk.nama)
                        .font(.title)
                        .fontWeight(.bold)
                    
                    Text("Rp \(produk.harga.formatted())")
                        .font(.title2)
                        .foregroundColor(.blue)
                        .fontWeight(.semibold)
                    
                    Text(produk.deskripsi)
                        .font(.body)
                        .foregroundColor(.secondary)
                }
                
                // Quantity stepper
                HStack {
                    Text("Jumlah:")
                        .font(.headline)
                    
                    Stepper("\(quantity)", value: $quantity, in: 1...10)
                }
                
                // Total price
                HStack {
                    Text("Total:")
                        .font(.headline)
                    Spacer()
                    Text("Rp \((produk.harga * quantity).formatted())")
                        .font(.title3)
                        .fontWeight(.bold)
                        .foregroundColor(.blue)
                }
                
                // Buy button
                Button(action: {
                    print("Membeli \(quantity)x \(produk.nama)")
                }) {
                    Text("🛒 Beli Sekarang")
                        .font(.headline)
                        .foregroundColor(.white)
                        .frame(maxWidth: .infinity)
                        .padding()
                        .background(Color.blue)
                        .cornerRadius(12)
                }
            }
            .padding()
        }
        .navigationTitle(produk.nama)
        .navigationBarTitleDisplayMode(.inline)
    }
}

// ===== Sheet / Modal Presentation =====
struct MainView: View {
    @State private var showProfile = false
    @State private var showSettings = false
    
    var body: some View {
        VStack(spacing: 20) {
            Text("🏠 Beranda")
                .font(.largeTitle)
            
            Button("👤 Profil") {
                showProfile = true
            }
            .buttonStyle(.borderedProminent)
            
            Button("⚙️ Pengaturan") {
                showSettings = true
            }
            .buttonStyle(.bordered)
        }
        .sheet(isPresented: $showProfile) {
            ProfilView()
        }
        .sheet(isPresented: $showSettings) {
            NavigationView {
                Text("Pengaturan Aplikasi")
                    .navigationTitle("Pengaturan")
                    .navigationBarTitleDisplayMode(.inline)
                    .toolbar {
                        ToolbarItem(placement: .navigationBarLeading) {
                            Button("Tutup") { showSettings = false }
                        }
                    }
            }
        }
    }
}
Diagram: SwiftUI Navigation
┌───────────────────────────────────────────────────────┐
│              SWIFTUI NAVIGATION                       │
│                                                       │
│  ┌──────────────────┐                                 │
│  │  NavigationStack │  ← Container utama              │
│  │                  │                                 │
│  │  ┌────────────┐  │                                 │
│  │  │ Home View  │  │  ← Root view                   │
│  │  │            │  │                                 │
│  │  │ [Link A] ──┼──┼──── push ──▶ ┌────────────┐   │
│  │  │ [Link B]   │  │              │ Detail View │   │
│  │  │ [Sheet] ───┼──┼── sheet ──▶ ┌────────────┐   │
│  │  └────────────┘  │              │ Modal View  │   │
│  └──────────────────┘              └────────────┘   │
│                                                       │
│  NavigationLink → Push (stack)                        │
│  .sheet() → Modal presentation                        │
│  .fullScreenCover() → Full screen modal               │
│  .navigationDestination() → Type-safe routing         │
└───────────────────────────────────────────────────────┘

7. Data Persistence

Menyimpan data secara lokal sangat penting agar aplikasi bisa berfungsi secara offline dan menyimpan pengaturan pengguna. Swift/iOS menyediakan beberapa opsi penyimpanan data lokal.

Opsi Penyimpanan Data di iOS

Metode Cocok Untuk Kapasitas
UserDefaultsPengaturan, flag, data kecilKecil (KB)
FileManagerFile, gambar, dokumenBesar (MB-GB)
Core DataDatabase relasional kompleksBesar
SwiftDataModern replacement untuk Core DataBesar
KeychainPassword, token, data sensitifKecil
SQLiteDatabase langsungBesar
Swift — Data Persistence
import SwiftUI

// ===== 1. UserDefaults (data kecil) =====
class AppSettings: ObservableObject {
    @AppStorage("username") var username: String = ""
    @AppStorage("isDarkMode") var isDarkMode: Bool = false
    @AppStorage("fontSize") var fontSize: Double = 16.0
    @AppStorage("language") var language: String = "id"
}

struct SettingsView: View {
    @StateObject private var settings = AppSettings()
    
    var body: some View {
        Form {
            Section("Profil") {
                TextField("Username", text: $settings.username)
            }
            
            Section("Tampilan") {
                Toggle("Dark Mode", isOn: $settings.isDarkMode)
                
                VStack {
                    Text("Font Size: \(Int(settings.fontSize))pt")
                    Slider(value: $settings.fontSize, in: 12...24, step: 1)
                }
            }
            
            Section("Bahasa") {
                Picker("Bahasa", selection: $settings.language) {
                    Text("Indonesia").tag("id")
                    Text("English").tag("en")
                    Text("日本語").tag("ja")
                }
            }
        }
        .navigationTitle("Pengaturan")
    }
}

// ===== 2. Codable — Simpan/Load JSON =====
struct Catatan: Codable, Identifiable {
    let id: UUID
    var judul: String
    var isi: String
    var tanggal: Date
}

class CatatanManager {
    private let fileName = "catatan.json"
    
    // Dapatkan path file di Documents directory
    private var fileURL: URL {
        FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            .appendingPathComponent(fileName)
    }
    
    // Simpan catatan ke file
    func simpan(_ catatan: [Catatan]) {
        do {
            let data = try JSONEncoder().encode(catatan)
            try data.write(to: fileURL)
            print("✅ \(catatan.count) catatan tersimpan")
        } catch {
            print("❌ Gagal menyimpan: \(error)")
        }
    }
    
    // Muat catatan dari file
    func muat() -> [Catatan] {
        do {
            let data = try Data(contentsOf: fileURL)
            let catatan = try JSONDecoder().decode([Catatan].self, from: data)
            return catatan
        } catch {
            print("📂 Tidak ada catatan tersimpan atau file corrupt")
            return []
        }
    }
}

// ===== 3. SwiftData (iOS 17+) =====
import SwiftData

@Model
class TaskItem {
    var id: UUID
    var title: String
    var isCompleted: Bool
    var createdAt: Date
    
    init(title: String) {
        self.id = UUID()
        self.title = title
        self.isCompleted = false
        self.createdAt = Date()
    }
}

struct TaskListView: View {
    @Environment(\.modelContext) private var modelContext
    @Query(sort: \TaskItem.createdAt, order: .reverse) private var tasks: [TaskItem]
    @State private var newTaskTitle = ""
    
    var body: some View {
        NavigationStack {
            VStack {
                HStack {
                    TextField("Tugas baru...", text: $newTaskTitle)
                        .textFieldStyle(.roundedBorder)
                    
                    Button("Tambah") {
                        guard !newTaskTitle.isEmpty else { return }
                        let task = TaskItem(title: newTaskTitle)
                        modelContext.insert(task)
                        newTaskTitle = ""
                    }
                    .buttonStyle(.borderedProminent)
                }
                .padding()
                
                List {
                    ForEach(tasks) { task in
                        HStack {
                            Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                                .foregroundColor(task.isCompleted ? .green : .gray)
                                .onTapGesture {
                                    task.isCompleted.toggle()
                                }
                            
                            Text(task.title)
                                .strikethrough(task.isCompleted)
                        }
                    }
                    .onDelete { indexSet in
                        for index in indexSet {
                            modelContext.delete(tasks[index])
                        }
                    }
                }
            }
            .navigationTitle("📋 Tugas (SwiftData)")
        }
    }
}

// App entry point dengan SwiftData
@main
struct TaskApp: App {
    var body: some Scene {
        WindowGroup {
            TaskListView()
        }
        .modelContainer(for: TaskItem.self)
    }
}
💡 Kapan Menggunakan Apa?

Gunakan UserDefaults untuk pengaturan sederhana, Codable + FileManager untuk data JSON, SwiftData untuk database relasional (iOS 17+), dan Core Data untuk proyek yang perlu mendukung iOS versi lama. Untuk data sensitif seperti password dan token, selalu gunakan Keychain.

8. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Swift dan iOS Development:

Pertanyaan 1: Apa perbedaan antara 'var' dan 'let' di Swift?

a) var untuk angka, let untuk teks
b) var bisa diubah (mutable), let tidak bisa diubah (immutable)
c) var untuk public, let untuk private
d) Tidak ada perbedaan

Pertanyaan 2: Apa itu Optional di Swift?

a) Variabel yang hanya bisa berisi angka
b) Variabel yang bisa berisi nilai atau nil (tidak ada nilai)
c) Variabel yang tidak bisa diubah
d) Variabel yang hanya bisa diakses di dalam class

Pertanyaan 3: Apa perbedaan utama antara UIKit dan SwiftUI?

a) UIKit untuk Android, SwiftUI untuk iOS
b) UIKit menggunakan pendekatan imperative, SwiftUI menggunakan pendekatan declarative
c) UIKit lebih baru dari SwiftUI
d) UIKit tidak mendukung Auto Layout

Pertanyaan 4: Apa fungsi dari @State di SwiftUI?

a) Mengambil data dari API
b) Menyimpan state yang dimiliki oleh view dan memicu re-render saat berubah
c) Membuat view menjadi read-only
d) Mengirim data ke view lain

Pertanyaan 5: Metode penyimpanan apa yang cocok untuk data sensitif seperti password dan token?

a) UserDefaults
b) Core Data
c) SwiftData
d) Keychain