-
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.
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
Resources
- Destination Video
- Elevating your iPad app with a tab bar and sidebar
- Enhancing your app’s content with tab navigation
- Forum: UI Frameworks
Related Videos
WWDC22
WWDC20
-
Download
Hi there, welcome to "Elevate your tab and sidebar experience in iPadOS." My name is Andy, and I'm a UIKit engineer. In this video, I'll guide you through enhancements to the tab bar and sidebar that will elevate your app and help you to put content at the forefront. Tab bars have been a core navigation pattern on iPhone and iPad since their debut. Tabs categorize the content of your app into different sections. Tab bars let people quickly switch between tabs while preserving the current state within each tab. For example, in the Clock app, there are four distinct tabs in the tab bar: World Clock, Alarms, Stopwatch, and Timers. In iPadOS 18, tab bars are refreshed with a new compact look that reduces the amount of unused vertical and horizontal space, bringing more content to the forefront.
The bar now sits at the top of the app, where it is closer to other navigation controls, making it easier to reach. It also shares a space with the top bar, allowing more of the app's content to be visible. Tabs should be consistent between iPhone and iPad to allow people to navigate easily no matter what device they are using. Avoid adding too many tabs. Too many choices make it harder for people to locate information within your app.
Like tab bars, sidebars help people navigate your app by providing quick access to top-level destinations. They can surface top-level collections of content, like folders or playlists, to make navigation more efficient. On iPadOS 17 and earlier, sidebars were constructed using split views with an outline view in its leading column. New in iPadOS 18, tab bars can now optionally be displayed as a sidebar with several enhancements that will make your sidebar better than ever. When the sidebar is hidden, it animates back into the tab bar, bringing focus back to the content represented by the tab. People can navigate with the tab bar without needing to re-open the sidebar. Tab bars show top-level sections of your app, and sidebars can optionally surface more sections of the same app hierarchy. Adopting the tab bar and sidebar is great for content-focused apps with rich hierarchies.
For best practices on tab bar navigation, watch "Explore navigation design on iOS." And to learn more about sidebars, watch "Designed for iPad." Content-rich apps should not just be powerful, but also personal. The new sidebar supports customization features that allow people to show or hide individual tabs, or to reorder them. The tab bar allows people to add their favorite tabs through drag and drop, making it easy to personalize their experience.
Adopting the tab bar with your existing sidebar brings together the best of both worlds, and enables new ways to customize and personalize your app. First, I'll go over features in the new tab bar and sidebar, and what to consider when adopting them. Next, I'll talk about supporting user customization to enable a more personal touch in your app.
Finally, I'll cover how these features look on different platforms, and what you need to consider when building for a multi-platform experience. I'll start with tab bars and sidebars. When you compile with the iPadOS 18 SDK, your existing tab bar apps will be updated to the new look without any code changes. With this updated look, the tab bar shares a safe area with the navigation bar for a more integrated appearance.
Toolbar items from the navigation bar will automatically move to overflow if there is not enough room to show alongside the tab bar.
New in iOS 18, TabView has a new syntax in SwiftUI to make it easier to catch common errors at build time. You now declare a Tab struct with a title, image, and its content view.
Optionally, include a selection value in the Tab to enable programmatic selection. This new syntax ensures that all tabs have the same selection type and that the type matches the TabView itself.
New in iOS 18, UIKit has new API to better describe your app hierarchy to UITabBarController. Create UITab to represent each top-level section of the app, and assign them to the tabs property on tabBarController. Changes made to the UITab instance are reflected immediately where the tab is displayed.
Tab bars prefer filled glyphs, and sidebars prefer outlined glyphs. When using symbol images for your tabs, use the outline variant.
The system will automatically select the filled variant when displayed in the tab bar. For example, in the Music app, the Browse tab uses the square.grid.2x2 symbol, which is an outlined glyph. In the tab bar, the filled variant is displayed without specifying a different image.
Search is a common feature in all apps and is immediately recognizable with its magnifying glass symbol. Using the search role Tab in SwiftUI or UISearchTab in UIKit, the system will configure a tab with a default title, image, and pinned placement. With the pinned placement, the tab is always available in the trailing edge of the tab bar.
Sidebars are great in iPad apps that have rich hierarchies to surface additional collections of content. The new Tab and UITab API enable apps to represent their app's structure for both the tab bar and sidebar. To enable the sidebar with TabView in SwiftUI: First, set the tabViewStyle to be sidebarAdaptable.
Next, use TabSection to represent a group in the sidebar.
Tabs appear in the declared order in the tab bar. In the sidebar, sections are sorted after individual tabs. In this example, Search will appear before the two TabSections in the sidebar.
To enable the sidebar with UITabBarController in UIKit Set the tabBarController's mode to tabSidebar.
Similar to TabSections, use UITabGroup to represent a collection of child tabs that belong to a single group.
Groups with dynamic content are updated by changing the group's children directly.
You can also add actions to sections in the sidebar to provide conveniences for common tasks, like creating a new Station in Podcasts.
Tabs are also drop destinations. This allows for direct additions into tabs in the sidebar or tab bar through drag and drop, like adding a photo into a collection.
To support drop destinations on Tabs in SwiftUI use the dropDestination modifier on the Tab with the receiver type.
To support drop destinations on UITabs in UIKit, implement two methods from UITabBarControllerDelegate. First, if the drop can be received, return a valid drop operation from operationForAcceptingItemsFromDropSession.
Next, to receive the data, load it from the drop session from acceptItemsFromDropSession.
Beyond actions and drop destinations, there are many new APIs to customize your sidebar. You can customize the sidebar's header and footer; or, add swipe actions or context menus to tabs. Additionally, you can show popovers from tabs, and they will be anchored to wherever the tab is shown. Describing your app with tabs is just the first step. Next, I'll go over adding user customization to make your app more personal.
Enable customization to allow people to configure the sidebar to their specific needs. People expect to hide non-essential tabs or to rearrange groups in the sidebar. Customizations to the order and visibility are automatically persisted.
Tab bar customization is similar to the toolbar customizations feature introduced in iPadOS 16.
Items in the tab bar can be customized through drag and drop, either by adding from the sidebar, or dragging them off. Tabs have a preferred placement to determine its customization and visibility preferences.
The tab bar contains three sections The fixed section is designed for important destinations of your app. They appear before other items and disallow customization. Items in the customizable section can be rearranged. Additional items can also be added from the sidebar through drag and drop.
Items in the pinned section, like search, are always available in the trailing edge of the tab bar.
You can use a sidebarOnly placement to disallow a tab from being added to the tab bar, making it accessible only through the sidebar.
To enable user customization on a TabView in SwiftUI First, attach a TabViewCustomization to the TabView. This opts tabs within the TabView into various customization features. If you have other UI that should mirror sidebar customizations, read from TabViewCustomization to track the customized data.
To ensure that customization is persisted, add an AppStorage with an identifier onto the TabViewCustomization.
Next, associate a customizationID to allow the tab to participate in customization.
Now that I've enabled customization, I want to make sure that the most important tabs in the app, like "Watch Now" or "Library" are fixed and always available.
To disable customization on individual tabs, use the customizationBehavior modifier. This modifier allows you to control how tabs behave in the sidebar and tab bar. The "Watch Now" tab is important to the app's functionality, so I'll disable customization for both the sidebar and tab bar. Tabs that cannot be customized don't need a customizationID.
Similarly, tabs can also be hidden, allowing flexibility in what default destinations are surfaced in your app. Use the defaultVisibility modifier to hide tabs from the sidebar or tab bar.
Now that I've disabled customization on "Watch Now" and "Library", it is now clear which tabs are important to the app, and which ones can be customized.
To enable customization of tabs in UIKit Set allowsHiding to true to allow non-essential tabs to be hidden. The current visibility can be determined by the isHidden property on UITab.
When tabs are assigned to the tabBarController, their stored customizations are applied.
Use the preferredPlacement property to control the tab's customization behavior and visibility in the tab bar.
To allow rearrangement of tabs within groups, set allowsReordering to true. The customized order is determined by the displayOrderIdentifiers property.
When customization is completed, UIKit will call these two UITabBarControllerDelegate methods to notify you of customization changes to the visibility and order of tabs.
Customization allows people to easily create shortcuts to their favorite content, and fit both the sidebar and tab bar to their needs. These APIs make it easy to build tab view apps across Apple platforms. I'll go over some platform considerations and adjustments that you can make to build the best experience on each platform. In macOS Sequoia, if your TabView or TabBarController supports a sidebar, then it will adopt the standard Mac sidebar appearance. Tabs in the sidebar can be reordered through drag and drop, just like on iPad.
On visionOS 2, the tab bar displays in an ornament on the leading edge of the window for root tabs. With the new Tab and UITab API, the system will automatically select the filled variant for symbols when displayed in tab bar, just like on iOS.
With TabSection or UITabGroup, a sidebar will also be added to display alongside the group's content for secondary navigation within the group.
On tvOS 18, SwiftUI apps can use the TabView and TabSection API to adopt the new collapsible sidebar.
With Tab and UITab, it is now easier than ever to build incredible, easy-to-navigate, content-rich apps across Apple platforms. So what's next? Make sure your app looks great with the new tab bar. Explore how the new sidebar can elevate your app And support user customization to enable a new level of personalization in your app. Thank you for watching. Make sure to comment below if you prefer tabs or spaces.
-
-
4:27 - TabView updates in SwiftUI
TabView { Tab("Watch Now", systemImage: "play") { WatchNowView() } Tab("Library", systemImage: "books.vertical") { LibraryView() } // ... }
-
4:58 - UITabBarController updates in UIKIt
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() }, // ... ]
-
5:58 - Search tab
// SwiftUI Tab(role: .search) { SearchView() } // UIKit let searchTab = UISearchTab { SearchViewController() }
-
6:41 - Adding a sidebar in SwiftUI
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)
-
7:16 - Adding a sidebar in UIKit
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 // ... }, ]
-
7:35 - Updating a tab group in UIKit
let collectionsGroup = UITabGroup( title: "Collections", image: UIImage(systemName: "folder"), identifier: "Tabs.CollectionsGroup" children: self.collectionsTabs()) { _ in // ... } let newCollection = UITab(...) collectionsGroup.children.append(newCollection)
-
7:45 - Sidebar actions
TabSection(...) { // ... } .sectionActions { Button("New Station", ...) { // action } } // UIKit let tabGroup = UITabGroup(...) tabGroup.sidebarActions = [ UIAction(title: "New Station", ...) { _ in // action }, ]
-
8:12 - Drop destinations in SwiftUI
Tab(collection.name, image: collection.image) { CollectionDetailView(collection) } .dropDestination(for: Photo.self) { photos in // Add 'photos' to the specified collection }
-
8:24 - Drop destinations in UIKit
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 } }
-
10:45 - TabView customization in SwiftUI
"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)
( -
11:40 - Customization behavior and visibility in SwiftUI
Tab("Watch Now", systemImage: "play", value: .watchNow) { // ... } .customizationBehavior(.disabled, for: .sidebar, .tabBar) Tab("Optional Tab", ...) { // ... } .customizationID("Tab.example.optional") .defaultVisibility(.hidden, for: .tabBar)
-
12:38 - Tab customization in UIKit
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 = [...]
-
12:39 - Observing customization changes in UIKit
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. }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.