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 Tinggi | Swift mendekati performa C++ berkat LLVM compiler dan optimisasi yang canggih |
| Type Safety | Sistem tipe yang kuat mencegah banyak bug saat compile time |
| Memory Safety | Automatic Reference Counting (ARC) dan optional handling yang aman |
| Sintaks Modern | Kode yang lebih ringkas dan mudah dibaca dibanding Objective-C |
| Ekosistem Apple | Akses ke seluruh framework Apple: UIKit, SwiftUI, CoreML, ARKit, dll. |
| Komunitas Besar | Dokumentasi lengkap dari Apple, komunitas aktif, banyak tutorial |
| Pasar yang Menguntungkan | App Store menghasilkan revenue lebih tinggi per aplikasi dibanding Play Store |
Swift vs Alternatif Mobile
| Aspek | Swift (iOS) | Kotlin (Android) | Flutter (Cross-platform) |
|---|---|---|---|
| Platform | iOS, macOS, watchOS | Android, Server | iOS, Android, Web, Desktop |
| Bahasa | Swift | Kotlin | Dart |
| UI Framework | SwiftUI, UIKit | Jetpack Compose | Flutter Widget |
| Performa | 🟢 Native | 🟢 Native | 🟡 Near-native |
| Learning Curve | 🟡 Sedang | 🟡 Sedang | 🟡 Sedang |
| Hot Reload | 🟡 Terbatas | 🟡 Terbatas | 🟢 Ya |
┌───────────────────────────────────────────────────────┐ │ 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 │ └───────────────────────────────────────────────────────┘
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
// ===== 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
// ===== 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
// ===== 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 Editor | Editor kode dengan syntax highlighting, autocomplete, dan refactoring |
| Interface Builder | Desain UI secara visual (drag & drop) untuk UIKit |
| SwiftUI Preview | Preview real-time untuk SwiftUI tanpa build |
| iOS Simulator | Menjalankan aplikasi di simulator iPhone/iPad |
| Instruments | Profiling dan performance analysis |
| Asset Catalog | Mengelola gambar, warna, dan app icons |
| Playgrounds | Environment interaktif untuk eksperimen kode Swift |
Struktur Proyek iOS
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
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
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 |
|---|---|---|
| Paradigma | Imperative | Declarative |
| Kode | Lebih banyak boilerplate | Lebih ringkas |
| Preview | Build & Run | Real-time preview |
| Minimum iOS | iOS 2+ | iOS 13+ |
| Stabilitas | 🟢 Sangat stabil | 🟡 Terus berkembang |
| Komponen | Sangat lengkap | Berkembang pesat |
| Rekomendasi | Proyek existing/legacy | Proyek baru |
SwiftUI Fundamentals
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..
6. Navigation & Routing
Navigation adalah elemen fundamental dalam aplikasi mobile. SwiftUI menyediakan beberapa cara untuk navigasi antar halaman: NavigationStack, NavigationLink, dan sheet untuk modal presentation.
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 }
}
}
}
}
}
}
┌───────────────────────────────────────────────────────┐ │ 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 |
|---|---|---|
| UserDefaults | Pengaturan, flag, data kecil | Kecil (KB) |
| FileManager | File, gambar, dokumen | Besar (MB-GB) |
| Core Data | Database relasional kompleks | Besar |
| SwiftData | Modern replacement untuk Core Data | Besar |
| Keychain | Password, token, data sensitif | Kecil |
| SQLite | Database langsung | Besar |
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)
}
}
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: