Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2024 DesignSwiftUI & UI Frameworks

WWDC24 · 16 min · Design / SwiftUI & UI Frameworks

Elevate your tab and sidebar experience in iPadOS

iPadOS 18 introduces a new navigation system that gives people the flexibility to choose between using a tab bar or sidebar. The newly redesigned tab bar provides more space for content and other functionality. Learn how to use SwiftUI and UIKit to enable customization features – like adding, removing and reordering tabs – to enable a more personal touch in your app.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

  • 0:00 — Introduction
  • 0:52 — Tab bar and sidebar refresh
  • 3:56 — Tab bar and sidebar features
  • 4:28 — Tab bar SwiftUI updates
  • 5:00 — Tab bar UIKit updates
  • 5:58 — Search tab
  • 6:41 — Enable sidebar with TabView in SwiftUI
  • 7:16 — Enable sidebar with UITabBarController in UIKit
  • 7:46 — Sidebar actions
  • 8:13 — Drop destinations on Tabs in SwiftUI
  • 8:25 — Drop destinations on UITabs in UIKit
  • 9:15 — User customization
  • 10:45 — Enable customization in SwiftUI
  • 12:38 — Enable customization in UIKit
  • 13:52 — Platform considerations

Code shown on screen · 13 snippets

TabView updates in SwiftUI swift · at 4:27 ↗
TabView {
    Tab("Watch Now", systemImage: "play") {
        WatchNowView()
    }
    Tab("Library", systemImage: "books.vertical") {
        LibraryView()
    }
    // ...
}
UITabBarController updates in UIKIt swift · at 4:58 ↗
tabBarController.tabs = [
    UITab(title: "Watch Now", image: UIImage(systemName: "play"), identifier: "Tabs.watchNow") { _ in
       WatchNowViewController()
    },
    UITab(title: "Library", image: UIImage(systemName: "books.vertical"), identifier: "Tabs.library") { _ in
       LibraryViewController()
    },
    // ...
]
Search tab swift · at 5:58 ↗
// SwiftUI
Tab(role: .search) {
    SearchView()
}

// UIKit
let searchTab = UISearchTab {
    SearchViewController()
}
Adding a sidebar in SwiftUI swift · at 6:41 ↗
TabView {
    Tab("Watch Now", systemImage: "play") {
        // ...
    }
    Tab("Library", systemImage: "books.vertical") {
        // ...
    }
    // ...
    TabSection("Collections") {
        Tab("Cinematic Shots", systemImage: "list.and.film") {
            // ...
        }
        Tab("Forest Life", systemImage: "list.and.film") {
            // ...
        }
        // ...
    }
    TabSection("Animations") {
        // ...
    }
    Tab(role: .search) {
        // ...
    }
}
.tabViewStyle(.sidebarAdaptable)
Adding a sidebar in UIKit swift · at 7:16 ↗
let collectionsGroup = UITabGroup(
    title: "Collections",
    image: UIImage(systemName: "folder"),
    identifier: "Tabs.CollectionsGroup"
    children: self.collectionsTabs()) { _ in
        // ...
}

tabBarController.mode = .tabSidebar
tabBarController.tabs = [
    UITab(title: "Watch Now", ...) { _ in
        // ...
    },
    UITab(title: "Library", ...) { _ in
        // ...
    },
    // ...
    collectionsGroup,
    UITabGroup(title: "Animations", ...) { _ in
        // ...
    },
    UISearchTab { _ in
        // ...
    },
]
Updating a tab group in UIKit swift · at 7:35 ↗
let collectionsGroup = UITabGroup(
    title: "Collections",
    image: UIImage(systemName: "folder"),
    identifier: "Tabs.CollectionsGroup"
    children: self.collectionsTabs()) { _ in
        // ...
}


let newCollection = UITab(...)
collectionsGroup.children.append(newCollection)
Sidebar actions swift · at 7:45 ↗
TabSection(...) {
    // ...
}
.sectionActions {
    Button("New Station", ...) {
        // action
    }
}

// UIKit

let tabGroup = UITabGroup(...)
tabGroup.sidebarActions = [
    UIAction(title: "New Station", ...) { _ in
        // action
    },
]
Drop destinations in SwiftUI swift · at 8:12 ↗
Tab(collection.name, image: collection.image) {
    CollectionDetailView(collection)
}
.dropDestination(for: Photo.self) { photos in
    // Add 'photos' to the specified collection
}
Drop destinations in UIKit swift · at 8:24 ↗
func tabBarController(
    _ tabBarController: UITabBarController,
    tab: UITab, operationForAcceptingItemsFrom dropSession: any UIDropSession
) -> UIDropOperation {
    session.canLoadObjects(ofClass: Photo.self) ? .copy : .cancel
}

func tabBarController(
    _ tabBarController: UITabBarController,
    tab: UITab, acceptItemsFrom dropSession: any UIDropSession) {
    session.loadObjects(ofClass: Photo.self) { photos in
        // Add 'photos' to the specified collection
    }
}
TabView customization in SwiftUI swift · at 10:45 ↗
@AppStorage("MyTabViewCustomization")
private var customization: TabViewCustomization

TabView {
    Tab("Watch Now", systemImage: "play", value: .watchNow) {
        // ...
    }
    .customizationID("Tab.watchNow")
    // ...
    TabSection("Collections") {
        ForEach(MyCollectionsTab.allCases) { tab in
            Tab(...) {
                // ...
            }
            .customizationID(tab.customizationID)
        }
    }
    .customizationID("Tab.collections")
    // ...
}
.tabViewCustomization($customization)
Customization behavior and visibility in SwiftUI swift · at 11:40 ↗
Tab("Watch Now", systemImage: "play", value: .watchNow) {
    // ...
}
.customizationBehavior(.disabled, for: .sidebar, .tabBar)


Tab("Optional Tab", ...) {
    // ...
}
.customizationID("Tab.example.optional")
.defaultVisibility(.hidden, for: .tabBar)
Tab customization in UIKit swift · at 12:38 ↗
let myTab = UITab(...)
myTab.allowsHiding = true
print(myTab.isHidden)


// .default, .optional, .movable, .pinned, .fixed, .sidebarOnly
myTab.preferredPlacement = .fixed


let myTabGroup = UITabGroup(...)
myTabGroup.allowsReordering = true
myTabGroup.displayOrderIdentifiers = [...]
Observing customization changes in UIKit swift · at 12:39 ↗
func tabBarController(_ tabBarController: UITabBarController, visibilityDidChangeFor tabs: [UITab]) {
    // Read 'tab.isHidden' for the updated visibility.
}

func tabBarController(_ tabBarController: UITabBarController, displayOrderDidChangeFor group: UITabGroup) {
    // Read 'group.displayOrderIdentifiers' for the updated order.
}

Resources