Panduan Lengkap TabView Baru di SwiftUI iOS 26: Liquid Glass, Bottom Accessory, dan Minimize Behavior
Pelajari semua fitur baru TabView di SwiftUI iOS 26 — dari Liquid Glass otomatis, search tab role, bottom accessory ala Now Playing, sampai minimize behavior saat scroll. Lengkap dengan contoh kode dan best practices.

Kalau kamu developer iOS, pasti sudah akrab banget dengan TabView. Komponen navigasi paling fundamental di hampir setiap aplikasi — dari Instagram sampai Apple Music. Tapi di iOS 26, Apple nggak cuma kasih cat baru. Mereka benar-benar merombak pengalaman tab bar secara mendasar.
Dan jujur, hasilnya cukup bikin kagum.
Dengan hadirnya Liquid Glass sebagai bahasa desain baru, tab bar sekarang berubah jadi semacam floating dock transparan yang bisa kamu lihat konten di belakangnya. Tapi perubahan visual itu cuma permukaan. Yang lebih menarik justru API-API baru yang datang bersamanya: bottom accessory untuk menaruh kontrol penting di atas tab bar, minimize behavior yang membuat tab bar menyingkir saat user scroll, dan search tab role yang mengubah tab pencarian jadi search field langsung.
Nah, di artikel ini kita bakal bedah semuanya satu per satu. Dari setup dasar sampai kombinasi fitur-fitur baru yang bisa bikin navigasi aplikasi kamu terasa jauh lebih modern. Semua contoh kode bisa langsung kamu coba di Xcode 26 — jadi siapkan project baru kalau mau ikutan.
Persyaratan Sebelum Mulai
Sebelum kita mulai coding, pastikan environment kamu sudah siap:
- Xcode 26 atau lebih baru
- iOS 26 SDK
- Deployment target minimal iOS 26.0
- Import
SwiftUIdi file Swift kamu
Satu hal yang perlu diingat — beberapa fitur baru seperti tabBarMinimizeBehavior dan tabViewBottomAccessory memerlukan iOS 26 secara strict. Nggak ada backward compatibility. Kalau kamu masih perlu support versi lama, pakai #available check untuk fallback. Agak merepotkan memang, tapi begitulah.
Tab API Modern: Tinggalkan tabItem, Pakai Tab
Sebelum masuk ke fitur-fitur baru, ada satu hal penting yang harus kita bahas dulu. Untuk memanfaatkan semua kemampuan baru TabView di iOS 26, kamu harus pakai Tab API yang diperkenalkan di iOS 18. Masih pakai .tabItem lama? Beberapa fitur baru bakal nggak berfungsi sama sekali.
Cara Lama (Jangan Pakai Lagi)
// ❌ Cara lama — nggak support fitur iOS 26 baru
TabView {
HomeView()
.tabItem {
Label("Beranda", systemImage: "house")
}
SettingsView()
.tabItem {
Label("Pengaturan", systemImage: "gear")
}
}
Cara Baru (Wajib Pakai)
// ✅ Cara baru — support penuh fitur iOS 26
TabView {
Tab("Beranda", systemImage: "house") {
HomeView()
}
Tab("Pengaturan", systemImage: "gear") {
SettingsView()
}
}
Perbedaannya? Dengan Tab API baru, setiap tab adalah entitas yang jelas — punya title, icon, dan opsionalnya role. Ini juga yang memungkinkan fitur seperti search tab role, sidebar adaptif di iPad, dan drag-to-reorder. Honestly, migrasinya nggak susah kok — biasanya cuma perlu beberapa menit per tab.
Liquid Glass: Tab Bar Otomatis Jadi Cantik
Ini kabar baiknya: kamu nggak perlu ngapa-ngapain untuk dapet Liquid Glass di tab bar. Serius. Selama kamu compile aplikasi dengan Xcode 26 dan jalankan di iOS 26, tab bar otomatis berubah jadi floating dock dengan efek kaca transparan.
struct MainTabView: View {
var body: some View {
TabView {
Tab("Beranda", systemImage: "house") {
NavigationStack {
ScrollView {
LazyVStack(spacing: 16) {
ForEach(0..<20) { i in
RoundedRectangle(cornerRadius: 12)
.fill(.blue.opacity(0.1))
.frame(height: 80)
.overlay {
Text("Item \(i)")
.font(.headline)
}
}
}
.padding()
}
.navigationTitle("Beranda")
}
}
Tab("Aktivitas", systemImage: "chart.bar") {
Text("Aktivitas")
}
Tab("Profil", systemImage: "person") {
Text("Profil")
}
}
}
}
Saat dijalankan, kamu bakal melihat tab bar nggak lagi berupa bar solid putih atau hitam di bagian bawah. Sekarang dia jadi floating dengan efek kaca — konten list di belakangnya terlihat blur melalui material glass. Efeknya konsisten dengan navigation bar dan toolbar yang juga mendapat treatment Liquid Glass di iOS 26.
Oh, dan satu hal yang mungkin bikin kaget: di iPad, tab bar sekarang dipindahkan ke bagian atas. Posisi tab bar di bawah layar cuma untuk iPhone. Ini perubahan yang cukup signifikan kalau kamu punya layout yang bergantung pada posisi tab bar — jadi worth dicek ulang.
Search Tab Role: Tab Pencarian yang Berubah Jadi Search Field
Oke, ini salah satu fitur paling keren di TabView iOS 26 menurut saya. Dengan search tab role, kalau kamu punya tab pencarian, kamu tinggal kasih role .search dan iOS akan memperlakukannya secara spesial.
struct AppTabView: View {
@State private var searchText = ""
var body: some View {
TabView {
Tab("Beranda", systemImage: "house") {
NavigationStack {
HomeView()
.navigationTitle("Beranda")
}
}
Tab("Koleksi", systemImage: "books.vertical") {
NavigationStack {
CollectionView()
.navigationTitle("Koleksi")
}
}
Tab("Favorit", systemImage: "heart.fill") {
NavigationStack {
FavoritesView()
.navigationTitle("Favorit")
}
}
// Search tab dengan role .search
Tab(role: .search) {
NavigationStack {
SearchResultsView(query: searchText)
.navigationTitle("Pencarian")
.searchable(text: $searchText, prompt: "Cari artikel...")
}
}
}
}
}
Jadi apa yang terjadi saat kamu pakai Tab(role: .search)?
- Tab pencarian dipisahkan secara visual dari tab-tab lainnya di tab bar
- Icon magnifying glass dikunci oleh sistem — kamu nggak bisa ganti (mau nggak mau)
- Saat dipilih, tab pencarian bertransformasi menjadi search field langsung di tab bar
- Di iPhone, search field muncul di bagian bawah layar — posisi yang gampang dijangkau ibu jari
- Di iPad, search field muncul sebagai floating glass container di pojok kanan atas
Perilaku ini mirip dengan yang ada di App Store dan Apple Music. Dan yang bikin senang, kamu dapet semua ini cuma dengan menambahkan role: .search — tanpa perlu kustomisasi manual apapun.
Meminimalkan Search dengan searchToolbarBehavior
Kalau pencarian bukan fitur utama di aplikasi kamu, kamu bisa bikin search field diminimalkan jadi tombol toolbar:
Tab(role: .search) {
NavigationStack {
SearchView()
.searchable(text: $searchText)
.searchToolbarBehavior(.minimize)
}
}
Dengan .searchToolbarBehavior(.minimize), search field nggak langsung muncul full — user harus tap tombol dulu untuk membukanya. Cocok banget untuk aplikasi yang punya fitur pencarian tapi nggak mau terlalu menonjolkannya.
Tab Bar Minimize Behavior: Tab Bar yang Tahu Kapan Harus Minggir
Nah, ini fitur favorit saya secara pribadi. Dengan tabBarMinimizeBehavior, tab bar bisa otomatis mengecilkan diri saat user scroll — memberikan lebih banyak ruang untuk konten. Tab bar nggak hilang sepenuhnya, tapi menyusut jadi satu ikon kecil di pojok bawah. Rasanya sangat Apple banget.
Setup Dasar
struct ContentView: View {
var body: some View {
TabView {
Tab("Feed", systemImage: "text.bubble") {
ArticleFeedView()
}
Tab("Notifikasi", systemImage: "bell") {
NotificationView()
}
Tab("Pengaturan", systemImage: "gear") {
SettingsView()
}
}
.tabBarMinimizeBehavior(.onScrollDown)
}
}
Opsi Minimize yang Tersedia
Ada empat opsi yang bisa kamu pilih:
.automatic— Sistem yang menentukan. Di iOS, tab bar tetap terlihat. Di visionOS, minimize saat user melihat ke arah lain. Di macOS, minimize saat ruang window terbatas..onScrollDown— Tab bar mengecil saat user scroll ke bawah. Khusus iPhone..onScrollUp— Tab bar mengecil saat user scroll ke atas. Khusus iPhone..never— Tab bar selalu tampil penuh. Titik.
Penting: .onScrollDown dan .onScrollUp hanya berfungsi di iPhone. Di iPad dan platform lain, perilakunya bakal fallback ke .automatic. Saya sempat bingung pas pertama kali testing di iPad simulator dan minimize-nya nggak jalan — ternyata memang begitu by design.
Bagaimana Minimize Bekerja?
Saat minimize terpicu, ini yang terjadi secara berurutan:
- Tab bar menyusut dari bar penuh menjadi satu ikon tab yang aktif
- Ikon tersebut bergeser ke pojok kiri bawah layar
- Label teks tab dihilangkan untuk menghemat ruang
- Saat user scroll kembali (untuk
.onScrollDown), tab bar kembali ke ukuran penuh dengan animasi yang mulus
Fitur ini sangat berguna untuk aplikasi yang konten utamanya adalah list panjang atau feed — seperti aplikasi berita, media sosial, atau e-commerce. User bisa fokus browsing tanpa tab bar menghalangi pandangan.
Bottom Accessory: Kontrol Penting yang Selalu Terlihat
Pernah pakai Apple Music dan lihat bar "Now Playing" kecil di atas tab bar? Nah, sekarang kamu bisa bikin hal yang persis sama di aplikasi kamu sendiri dengan tabViewBottomAccessory. Saya sudah lama nunggu fitur ini, dan akhirnya datang juga.
Setup Dasar
struct MusicPlayerApp: View {
@State private var isPlaying = false
@State private var currentSong = "Lagu Favorit"
var body: some View {
TabView {
Tab("Beranda", systemImage: "house") {
HomeView()
}
Tab("Library", systemImage: "music.note.list") {
LibraryView()
}
Tab(role: .search) {
SearchView()
}
}
.tabViewBottomAccessory {
HStack {
Image(systemName: "music.note")
.foregroundStyle(.secondary)
Text(currentSong)
.font(.subheadline)
.fontWeight(.medium)
.lineLimit(1)
Spacer()
Button {
isPlaying.toggle()
} label: {
Image(systemName: isPlaying ? "pause.fill" : "play.fill")
.font(.title3)
}
Button {
// Next track
} label: {
Image(systemName: "forward.fill")
.font(.title3)
}
}
.padding(.horizontal)
}
}
}
Bottom accessory otomatis mendapat latar belakang Liquid Glass berbentuk kapsul. Kamu nggak perlu styling manual — sistem yang handle semuanya. Hasilnya terlihat native dan konsisten dengan desain Apple. Keren, kan?
Accessory yang Adaptif Berdasarkan Posisi
Yang lebih menarik lagi, kamu bisa mendeteksi posisi accessory saat ini dan menyesuaikan tampilan. Ada dua posisi: .expanded (saat tab bar penuh) dan .inline (saat tab bar diminimalkan).
struct AdaptiveAccessoryView: View {
@Environment(\.tabViewBottomAccessoryPlacement) var placement
var body: some View {
switch placement {
case .expanded:
// Tampilan lengkap saat tab bar expanded
HStack {
Image(systemName: "music.note")
VStack(alignment: .leading) {
Text("Sedang Diputar")
.font(.caption)
.foregroundStyle(.secondary)
Text("Judul Lagu yang Panjang")
.font(.subheadline)
.fontWeight(.medium)
}
Spacer()
PlaybackControls()
}
.padding(.horizontal)
default:
// Tampilan compact saat tab bar inline/minimized
HStack {
Image(systemName: "music.note")
Text("Sedang Diputar")
.font(.caption2)
}
}
}
}
Dengan memanfaatkan environment value tabViewBottomAccessoryPlacement, kamu bisa bikin accessory yang benar-benar adaptif — menampilkan info lengkap saat ruang tersedia, dan menyusut jadi compact saat tab bar diminimalkan. Detail kecil yang bikin aplikasi terasa polished.
Menggabungkan Semuanya: Contoh Aplikasi Lengkap
Oke, sekarang bagian yang seru. Mari kita gabungkan semua fitur yang sudah dibahas ke dalam satu contoh aplikasi yang realistis — aplikasi podcast player:
import SwiftUI
// MARK: - Model Data
struct Podcast: Identifiable {
let id = UUID()
let title: String
let host: String
let episodeCount: Int
let category: String
}
struct Episode: Identifiable {
let id = UUID()
let title: String
let duration: String
let date: String
}
// MARK: - Main App View
struct PodcastApp: View {
@State private var searchText = ""
@State private var isPlaying = false
@State private var currentEpisode = "Eps. 42: SwiftUI iOS 26"
let samplePodcasts = [
Podcast(title: "Swift Talk", host: "objc.io", episodeCount: 420, category: "Teknologi"),
Podcast(title: "Under the Radar", host: "Marco Arment", episodeCount: 310, category: "Teknologi"),
Podcast(title: "Stacktrace", host: "John Sundell", episodeCount: 230, category: "Teknologi"),
Podcast(title: "Launched", host: "Charlie Chapman", episodeCount: 85, category: "Indie Dev"),
Podcast(title: "Core Intuition", host: "Daniel Jalkut", episodeCount: 550, category: "Teknologi"),
]
var body: some View {
TabView {
// Tab 1: Beranda
Tab("Beranda", systemImage: "house") {
NavigationStack {
List(samplePodcasts) { podcast in
VStack(alignment: .leading, spacing: 4) {
Text(podcast.title)
.font(.headline)
Text(podcast.host)
.font(.subheadline)
.foregroundStyle(.secondary)
Text("\(podcast.episodeCount) episode • \(podcast.category)")
.font(.caption)
.foregroundStyle(.tertiary)
}
.padding(.vertical, 4)
}
.navigationTitle("Podcast Saya")
}
}
// Tab 2: Discover
Tab("Jelajahi", systemImage: "safari") {
NavigationStack {
Text("Temukan podcast baru")
.navigationTitle("Jelajahi")
}
}
// Tab 3: Library
Tab("Koleksi", systemImage: "books.vertical") {
NavigationStack {
Text("Podcast yang diunduh")
.navigationTitle("Koleksi")
}
}
// Tab 4: Search dengan role .search
Tab(role: .search) {
NavigationStack {
SearchResultsView(query: searchText)
.navigationTitle("Cari Podcast")
.searchable(
text: $searchText,
prompt: "Cari podcast atau episode..."
)
}
}
}
.tabBarMinimizeBehavior(.onScrollDown)
.tabViewBottomAccessory {
NowPlayingAccessory(
episodeTitle: currentEpisode,
isPlaying: $isPlaying
)
}
}
}
// MARK: - Now Playing Accessory
struct NowPlayingAccessory: View {
let episodeTitle: String
@Binding var isPlaying: Bool
@Environment(\.tabViewBottomAccessoryPlacement) var placement
var body: some View {
switch placement {
case .expanded:
expandedView
default:
compactView
}
}
private var expandedView: some View {
HStack(spacing: 12) {
RoundedRectangle(cornerRadius: 6)
.fill(.blue.gradient)
.frame(width: 40, height: 40)
.overlay {
Image(systemName: "waveform")
.foregroundStyle(.white)
}
VStack(alignment: .leading, spacing: 2) {
Text(episodeTitle)
.font(.subheadline)
.fontWeight(.medium)
.lineLimit(1)
Text("Swift Talk")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
HStack(spacing: 16) {
Button {
isPlaying.toggle()
} label: {
Image(systemName: isPlaying ? "pause.fill" : "play.fill")
.font(.title3)
}
Button {} label: {
Image(systemName: "forward.fill")
.font(.title3)
}
}
}
.padding(.horizontal)
}
private var compactView: some View {
HStack(spacing: 8) {
Image(systemName: "waveform")
.foregroundStyle(.blue)
Text(episodeTitle)
.font(.caption)
.lineLimit(1)
}
}
}
// MARK: - Search Results View
struct SearchResultsView: View {
let query: String
var body: some View {
if query.isEmpty {
ContentUnavailableView(
"Cari Podcast",
systemImage: "magnifyingglass",
description: Text("Ketik nama podcast atau episode")
)
} else {
List {
ForEach(0..<5) { i in
Text("Hasil untuk \"\(query)\" - \(i + 1)")
}
}
}
}
}
Di contoh ini, kita menggabungkan semuanya sekaligus:
- Tab API modern dengan
Tabview untuk setiap tab - Search tab role yang otomatis berubah jadi search field
- Minimize behavior yang menyembunyikan tab bar saat scroll ke bawah
- Bottom accessory yang adaptif berdasarkan posisi tab bar
- Dan tentu saja, semuanya otomatis mendapat Liquid Glass
Hasilnya? Aplikasi yang terasa sangat native iOS 26 — dengan navigasi yang responsif dan kontrol media yang selalu accessible. Kalau kamu coba jalankan di simulator, pasti langsung kerasa bedanya.
TabSection dan Sidebar Adaptif untuk iPad
Kalau kamu bikin aplikasi yang harus terlihat bagus di iPhone dan iPad, kamu perlu kenalan dengan TabSection dan style .sidebarAdaptable. Fitur ini memungkinkan tab bar di iPhone berubah jadi sidebar navigasi di iPad — tanpa kamu perlu menulis dua layout terpisah.
struct AdaptiveNavApp: View {
var body: some View {
TabView {
// Section pertama
TabSection("Utama") {
Tab("Beranda", systemImage: "house") {
HomeView()
}
Tab("Trending", systemImage: "flame") {
TrendingView()
}
}
// Section kedua
TabSection("Koleksi Saya") {
Tab("Favorit", systemImage: "heart.fill") {
FavoritesView()
}
Tab("Riwayat", systemImage: "clock") {
HistoryView()
}
Tab("Unduhan", systemImage: "arrow.down.circle") {
DownloadsView()
}
}
Tab(role: .search) {
SearchView()
}
}
.tabViewStyle(.sidebarAdaptable)
}
}
Nah, yang terjadi di masing-masing platform itu beda-beda:
- iPhone: Semua tab tampil sebagai tab bar biasa di bawah. Title section diabaikan.
- iPad (tab bar mode): Tab bar muncul di bagian atas dengan Liquid Glass. Mirip floating dock.
- iPad (sidebar mode): Tab-tab ditampilkan dalam sidebar dengan section header. User bisa switch antara tab bar dan sidebar.
Bonus: dengan .sidebarAdaptable, user iPad bahkan bisa drag tab dari sidebar ke tab bar, menyembunyikan tab yang nggak dibutuhin, dan menata ulang sidebar sesuai preferensi mereka. Semua ini gratis dari framework — kamu nggak perlu menulis logika drag-and-drop sendiri. Saya sampai cek ulang dokumentasinya karena nggak percaya ini beneran out-of-the-box.
Tips dan Best Practices
Setelah eksplorasi semua fitur baru ini (dan beberapa kali trial-error), berikut beberapa tips yang semoga bisa membantu kamu mengimplementasikannya lebih smooth:
1. Migrasi ke Tab API Sekarang
Kalau kamu masih pakai .tabItem, segera migrasi ke Tab view. Tanpa migrasi, kamu nggak bisa pakai search role, bottom accessory, maupun minimize behavior. Kabar baiknya, migrasinya sederhana dan biasanya cuma perlu beberapa menit per tab.
2. Gunakan Minimize Behavior dengan Bijak
Nggak semua aplikasi butuh minimize behavior. Pakai .onScrollDown kalau konten utama aplikasi kamu berupa list atau feed yang panjang. Untuk aplikasi dengan konten statis atau form input, mungkin lebih baik biarkan tab bar tetap terlihat dengan .never atau .automatic. Jangan terlalu agresif menyembunyikan navigasi — user butuh tahu di mana mereka berada.
3. Bottom Accessory Bukan untuk Semua Hal
Bottom accessory paling cocok untuk kontrol yang perlu selalu tersedia — seperti media player, progress download, atau status koneksi. Jangan pakai untuk informasi yang jarang dibutuhkan, karena itu malah memakan ruang layar yang berharga.
4. Test di Kedua Platform
Ini penting banget. Karena perilaku TabView cukup berbeda antara iPhone dan iPad di iOS 26, selalu test di kedua platform. Tab bar minimize hanya berfungsi di iPhone, sementara iPad punya behavior sidebar yang unik. Saya pernah kena masalah layout gara-gara cuma testing di satu simulator — jangan ulangi kesalahan saya.
5. Perhatikan Safe Area
Dengan hadirnya bottom accessory, safe area bawah akan bertambah. Pastikan konten scroll kamu punya padding yang cukup agar nggak tertutup. SwiftUI biasanya handle ini secara otomatis, tapi kalau kamu pakai custom layout, cek ulang inset-nya.
Troubleshooting Masalah Umum
Beberapa masalah yang mungkin kamu temui saat implementasi — dan cara mengatasinya:
Bottom Accessory Nggak Muncul
Pastikan kamu menggunakan Tab API (bukan .tabItem) dan modifier .tabViewBottomAccessory dipasang langsung ke TabView, bukan ke view di dalamnya. Ini salah satu gotcha yang paling sering bikin bingung.
Minimize Behavior Nggak Bekerja
Cek apakah kamu menjalankannya di iPhone simulator — fitur ini nggak berfungsi di iPad. Juga pastikan ada scrollable content di dalam tab (seperti List atau ScrollView) karena tanpa scroll event, minimize nggak akan terpicu.
Search Field Posisi Aneh di iPad
Di iOS 26, search field di iPad secara default muncul sebagai floating glass container di pojok kanan atas. Kalau kamu lebih suka posisi lama (di atas sidebar), tambahkan placement eksplisit di modifier .searchable:
.searchable(text: $searchText, placement: .sidebar)
FAQ
Apakah Liquid Glass di TabView bisa dinonaktifkan?
Nggak bisa secara langsung. Liquid Glass adalah bahasa desain default di iOS 26 dan diterapkan otomatis ke semua komponen sistem termasuk TabView. Kamu bisa saja bikin custom tab bar kalau benar-benar butuh tampilan berbeda, tapi untuk mayoritas aplikasi, disarankan mengikuti desain sistem agar konsisten dengan ekosistem iOS 26.
Apakah tabBarMinimizeBehavior berfungsi di iPad dan Mac?
Opsi .onScrollDown dan .onScrollUp hanya berfungsi di iPhone. Di iPad, macOS, dan platform lain, behavior akan fallback ke .automatic. Di visionOS, tab bar secara otomatis minimize saat user melihat ke arah lain — yang menurut saya cukup clever sebagai interaction pattern. Pastikan test di iPhone simulator untuk melihat efek minimize yang sesungguhnya.
Bisakah saya punya lebih dari satu bottom accessory?
Sayangnya, tidak. TabView hanya mendukung satu bottom accessory pada satu waktu. Kalau kamu butuh menampilkan banyak informasi, gabungkan semuanya dalam satu view accessory yang komprehensif. Manfaatkan tabViewBottomAccessoryPlacement environment value untuk bikin tampilan adaptif yang menampilkan lebih banyak info saat posisi expanded.
Apakah search tab role wajib pakai icon magnifying glass?
Ya, wajib. Saat kamu menggunakan Tab(role: .search), icon magnifying glass dikunci oleh sistem dan nggak bisa diganti. Ini keputusan desain Apple untuk menjaga konsistensi pengalaman pencarian di seluruh ekosistem. Kamu tetap bebas mengkustomisasi konten di dalam tab pencarian itu sendiri.
Bagaimana cara migrasi dari tabItem lama ke Tab API baru?
Gampang, kok. Ganti setiap blok .tabItem { Label("Judul", systemImage: "icon") } menjadi Tab("Judul", systemImage: "icon") { ... }. Pindahkan konten view ke dalam closure Tab. Untuk tab pencarian, tambahkan parameter role: .search. Keseluruhan proses biasanya cuma butuh waktu kurang dari 10 menit untuk aplikasi dengan 3-5 tab — jadi nggak ada alasan untuk menunda.


