Mendalami iOS native dengan Swift & SwiftUI: struktur proyek Xcode, declarative views, state management @State/@Binding, lifecycle scene, dan Human Interface Guidelines — sambil membangun halaman utama Fitku untuk iOS sebagai padanan dari versi Android

Setelah menguasai Android di episode 3, sekarang giliran sisi seberang: iOS dengan Swift & SwiftUI. Pola pikirnya mirip dengan Compose — UI deklaratif sebagai fungsi dari state — tetapi dengan bahasa (Swift), tooling (Xcode), dan design language (HIG) yang khas. Memahami kedua native platform membuat kalian mampu memilih strategi terbaik dan mengevaluasi solusi cross-platform dengan kepala dingin.
Penting: episode ini bisa diikuti penuh tanpa Mac jika kalian membaca kode dengan saksama — tapi idealnya jalankan di Xcode untuk benar-benar merasakan workflow-nya.
Proyek SwiftUI di Xcode terstruktur seperti ini:
Fitku/
├── Fitku.xcodeproj/
├── Fitku/
│ ├── FitkuApp.swift → entry point (@main)
│ ├── ContentView.swift → root view
│ ├── Models/ → data model (Workout, StepCount)
│ ├── ViewModels/ → observasi state
│ └── Assets.xcassets → gambar & warna
└── FitkuTests/ → unit testsSemua file ber-ekstensi .swift. Tidak ada manifest; Xcode mengelola build lewat project file. Build config (signing, deployment target) diatur lewat Signing & Capabilities di Xcode.
SwiftUI membangun UI dari View (struct conform View) dan modifier — padanan composable & modifier di Compose:
struct HomeView: View {
var userName: String
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Halo, \(userName)!")
.font(.title)
Text("Siap workout hari ini?")
.font(.subheadline)
Button("Mulai Workout") {
// aksi
}
.buttonStyle(.borderedProminent)
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}Perhatikan alur yang sama dengan Compose: struktur kolom, spacing, padding, tombol — bedanya hanya sintaks.
SwiftUI menyediakan beberapa property wrappers untuk state:
@State — state lokal milik satu view.@Binding — jembatan untuk mengubah state dari child view.@ObservedObject/@StateObject — state bersama dari class ObservableObject.@EnvironmentObject — state yang diinjeksikan ke banyak view.Contoh penghitung langkah:
struct StepCounterView: View {
@State private var steps = 0
var body: some View {
VStack(spacing: 12) {
Text("Langkah hari ini: \(steps)")
.font(.title2)
Button("Tambah langkah") {
steps += 1
}
.buttonStyle(.bordered)
}
}
}Important
Satu perbedaan penting dari Compose: di SwiftUI, @State didesain untuk state kecil milik view. Untuk state aplikasi (workout, profil), pakai ObservableObject di atas class @MainActor — kita bangun pola ini di episode 8 dan 9.
Entry point iOS adalah @main App struct yang mendeklarasikan Scene. SwiftUI menawarkan scenePhase untuk merespons perubahan foreground/background:
@main
struct FitkuApp: App {
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
HomeView()
}
.onChange(of: scenePhase) { _, newPhase in
switch newPhase {
case .active: print("aplikasi aktif")
case .inactive: print("transisi")
case .background:
// simpan state sebelum di-suspend
savePendingWorkout()
@unknown default: break
}
}
}
}Ingat pelajaran episode 2: iOS hanya memberi waktu singkat saat masuk background — gunakan momen ini untuk menyimpan progress.
HIG adalah pedoman desain resmi Apple. Prinsip inti yang wajib dipahami:
Implementasi praktisnya: pakai SF Symbols untuk ikon, sistem font (Dynamic Type), material & blur bawaan, dan komponen native (NavigationStack, TabView, List). Membangun UI "seenak" platform menuntut kalian mengikuti pola ini — bukan memaksakan pola dari platform lain.
Mari bangun halaman setara dengan versi Android episode 3:
struct HomeView: View {
@StateObject private var viewModel = HomeViewModel()
@State private var showingWorkout = false
var body: some View {
NavigationStack {
VStack(alignment: .leading, spacing: 16) {
Text("Halo, \(viewModel.userName)!")
.font(.title.bold())
Card {
VStack(alignment: .leading, spacing: 4) {
Text("Langkah hari ini")
.font(.caption)
.foregroundStyle(.secondary)
Text("\(viewModel.steps)")
.font(.system(size: 40, weight: .bold))
}
}
Button("Mulai Workout") {
showingWorkout = true
}
.font(.headline)
.frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent)
}
.padding()
.navigationTitle("Fitku")
.navigationDestination(isPresented: $showingWorkout) {
WorkoutView()
}
}
}
}MainActor.scenePhase: state hilang saat aplikasi di-suspend.ViewThatFits, LazyVGrid, dan ScrollView dengan benar.Tip
Kalian belum punya Mac dan hanya bisa memilih satu native platform untuk praktik sekarang? Fokus dulu di Android (episode 3). Konsep iOS tetap kalian pelajari di episode-episode selanjutnya lewat contoh kode — dan begitu ada akses Xcode, migrasi pemahamannya cepat karena pola deklaratifnya identik.
Pada episode 4 ini, kalian telah membangun fondasi iOS modern:
@main App, dan Scene/lifecycle dengan scenePhase.@State/@StateObject.Di episode 5 selanjutnya kita masuk dunia cross-platform: React Native + Expo — bagaimana satu codebase JavaScript/TypeScript bisa menjalankan aplikasi di Android & iOS, apa itu New Architecture (Fabric), dan membangun Fitku lintas platform dengan ekosistem JS. Sampai jumpa!