Создание многотабового интерфейса с Swiftui для лучшей навигации

SwiftUI, декларативная структура Apple для создания пользовательских интерфейсов на всех платформах Apple, обеспечивает мощный, но простой способ создания многотабовых интерфейсов. Навигация с вкладками является одним из наиболее распространенных шаблонов в мобильных и настольных приложениях, позволяя пользователям быстро переключаться между отдельными разделами без потери контекста. Независимо от того, создаете ли вы приложение с большим количеством контента, панель управления производительностью или панель настроек, TabView является основным компонентом для реализации навигации по вкладкам.

В этой статье рассказывается обо всем, что вам нужно знать о разработке многотабовых интерфейсов с SwiftUI. Начиная с основ TabView , мы рассмотрим настройку, управление состоянием, расширенные шаблоны, доступность, производительность и реальные примеры.

Понимание компонента TabView

В основе навигации по вкладкам в SwiftUI лежит TabView. Это вид контейнера, который представляет собой набор просмотров детей, каждый из которых связан с элементом вкладки. Когда пользователь нажимает на вкладку, отображается соответствующий вид, а панель вкладок показывает активное состояние с подсветкой.

Планировка и поведение

По умолчанию TabView отображает панель вкладок в нижней части экрана на iOS и в верхней части на macOS. iPadOS адаптируется к ориентации устройства, а watchOS использует сегментированный стиль. Вам не нужно управлять макетом самой панели вкладок; SwiftUI обрабатывает позиционирование, размер и анимацию выбора вкладки.

Таблички с символами SF

Apple рекомендует использовать символы SF для значков вкладок, потому что они идеально масштабируются на устройствах и в настройках доступности. Модификатор принимает конструктор просмотров, где вы обычно размещаете с именем символа SF и этикеткой .

TabView {
 ContentView()
 .tabItem {
 Label("Home", systemImage: "house.fill")
 }
}

Вид Label автоматически объединяет иконку и заголовок, обеспечивая правильное выравнивание и доступность меток. Вы всегда можете вернуться к отдельным и представлениям для большего контроля.

Создание базового многотабового интерфейса

Для создания простого приложения с вкладками требуется всего несколько строк кода. Давайте построим минимальный пример с тремя вкладками: Home, Search и Profile.

Шаг за шагом реализация

  1. Создайте новый вид SwiftUI (например, ) и добавьте TabView в качестве корня.
  2. Для каждого ребенка (или для ребенка) есть свой собственный вид (например, FLT:7).
  3. Прикрепите модификатор с соответствующим SF-символом и локализованной строкой.

Пример кода

struct ContentView: View {
 var body: some View {
 TabView {
 HomeView()
 .tabItem {
 Label("Home", systemImage: "house.fill")
 }

 SearchView()
 .tabItem {
 Label("Search", systemImage: "magnifyingglass")
 }

 ProfileView()
 .tabItem {
 Label("Profile", systemImage: "person.crop.circle")
 }
 }
 }
}

struct HomeView: View {
 var body: some View {
 Text("Welcome Home")
 }
}

struct SearchView: View {
 var body: some View {
 Text("Find what you need")
 }
}

struct ProfileView: View {
 var body: some View {
 Text("Your profile")
 }
}

Этот код создает рабочую панель вкладок с тремя элементами. Каждая вкладка показывает свой соответствующий контент при выборе. SwiftUI автоматически управляет анимацией активного состояния и перехода.

Настройка элементов Tab

Хотя внешний вид по умолчанию чистый, вам часто нужно настроить панель вкладок, чтобы соответствовать бренду вашего приложения или предоставить дополнительную обратную связь с пользователем.

Добавление значков и индикаторов

SwiftUI позволяет прикрепить значок к любому элементу вкладки с помощью модификатора на просмотре ребенка. На значках отображается небольшое количество или текст (например, количество непрочитанных уведомлений).

TabView {
 InboxView()
 .tabItem { Label("Inbox", systemImage: "tray.fill") }
 .badge(5) // Shows a red badge with "5"

 SettingsView()
 .tabItem { Label("Settings", systemImage: "gearshape") }
}

Баджеты обновляются автоматически при подключении к @State или наблюдаемому свойству.На iOS значок появляется как красный круг с номером; на других платформах он может отображаться по-другому.

Изменение внешнего вида вкладки (тонкие цвета и фон)

Чтобы изменить цвет акцента выбранной вкладки, установите модификатор на TabView (устаревший в новых версиях iOS) или используйте модификатор в iOS 15+:

TabView {
 // tabs...
}
.tint(.purple)

Для более продвинутой настройки, такой как пользовательский фон или форма, вы можете использовать через или построить полностью настраиваемую панель вкладок с помощью горизонтального стека кнопок (см. раздел панели вкладок на заказ позже). Однако, придерживаясь панели вкладок системы, когда это возможно, обеспечивает согласованное поведение с ожиданиями пользователей и жестами системного уровня.

Управление государством и отбор

Управление тем, какая вкладка активна, имеет важное значение для таких функций, как глубокая связь, пользовательские предпочтения или сброс приложения на первую вкладку после определенного действия.

Использование @State для выбора

Привяжите переменную @State того же типа, что и значения тегов к параметру TabView. Назначьте каждому ребенку вид целого числа (или любого значения) с помощью модификатора .

struct ContentView: View {
 @State private var selectedTab = 0

 var body: some View {
 TabView(selection: $selectedTab) {
 HomeView()
 .tabItem { Label("Home", systemImage: "house.fill") }
 .tag(0)

 SearchView()
 .tabItem { Label("Search", systemImage: "magnifyingglass") }
 .tag(1)

 ProfileView()
 .tabItem { Label("Profile", systemImage: "person.crop.circle") }
 .tag(2)
 }
 }
}

Когда изменяется (например, с помощью кнопки на домашнем экране), вид вкладки автоматически переключается на соответствующую вкладку. Этот подход также позволяет читать текущую вкладку в другом месте вашего приложения.

Программное переключение Tab

Вы можете изменить вкладки изнутри любого детского просмотра, передав привязку к переменной выбора. Например, кнопка «Перейти к настройкам» на домашней вкладке:

struct HomeView: View {
 @Binding var selectedTab: Int

 var body: some View {
 VStack {
 Text("Home")
 Button("Open Settings") {
 selectedTab = 2
 }
 }
 }
}

Интегрируйте это, пройдя связывание при создании детского просмотра внутри TabView. Это дает вам полный контроль над навигацией, не нарушая состояние вкладки.

Постоянный выбор вкладок с UserDefaults

Для удобного прикосновения запомните последнюю выбранную вкладку после перезагрузки приложения. Используйте , чтобы автоматически сохранять выбор на UserDefaults:

struct ContentView: View {
 @AppStorage("selectedTab") private var selectedTab = 0

 var body: some View {
 TabView(selection: $selectedTab) {
 // tabs...
 }
 }
}

Приложение теперь будет запущено на вкладке, которую пользователь в последний раз посещал.

Расширенные навигационные шаблоны

Приложения с несколькими вкладками часто должны интегрироваться с навигационными стеками, обрабатывать входящие URL-адреса или обмениваться данными между вкладками.

Объединяя TabView с NavigationView

Каждая вкладка может содержать свои собственные (или в более старом SwiftUI). Это позволяет сверлить подробные виды в вкладке, в то время как панель вкладок остается видимой.

struct HomeView: View {
 var body: some View {
 NavigationStack {
 List(1..<20) { item in
 NavigationLink("Item \(item)", value: item)
 }
 .navigationDestination(for: Int.self) { item in
 DetailView(item: item)
 }
 .navigationTitle("Home")
 }
 }
}

Поместите внутри детского просмотра вкладки, а не за пределами TabView , чтобы поддерживать отдельные навигационные стеки на вкладку.

Обработка глубоких ссылок

Когда ваше приложение получает универсальную ссылку или пользовательскую схему URL, вам может потребоваться перейти на определенную вкладку и необязательно перейти к подпросмотру. Используйте модификатор на TabView (или root view)) и проанализируйте URL, чтобы обновить состояние выбора или запустить навигацию.

TabView(selection: $selectedTab)
 .onOpenURL { url in
 // e.g., myapp://tab/2
 if let tabNumber = extractTab(from: url) {
 selectedTab = tabNumber
 }
 }

Для более сложных сценариев, объедините с траектория связывания.

Передача данных между вкладками

Поскольку каждая вкладка имеет свою собственную иерархию взглядов, передача данных между ними требует общего источника состояния.

Избегайте перенапряжения; сохраняйте поток данных однонаправленным, когда это возможно.

Доступность и пользовательский опыт

Доступный интерфейс вкладок имеет решающее значение для охвата самой широкой аудитории. SwiftUI обеспечивает встроенную поддержку доступности, но вы должны проверить и улучшить его.

Поддержка VoiceOver

Элементы вкладок автоматически получают ярлыки доступности из Лейбл или текста, который вы предоставляете. Тестируйте, чтобы каждая вкладка четко передавала свое назначение. Для пользовательских полос вкладок добавьте и , чтобы имитировать поведение системы.

Навигация по клавиатуре

На iPadOS с аппаратной клавиатурой пользователи могут перемещаться между вкладками с помощью командного номера или вкладки опции. TabView SwiftUI наследует это поведение автоматически. Убедитесь, что ваша реализация вкладки на заказ также поддерживает команды клавиатуры, добавив модификаторы .

Адаптивные прокладки

На iPhone в ландшафтном режиме панель вкладок может выглядеть по-разному. iOS 15 и более поздние позволяют панели вкладок компактнее, когда пространство ограничено. Вы можете настроить содержимое вкладок с помощью , чтобы обеспечить лучшее чтение или взаимодействие.

Настраиваемые панели вкладок с SwiftUI

Иногда вам нужен дизайн, который отклоняется от панели вкладок системы — например, плавающая кнопка посередине, изогнутая форма или анимированные значки.В то время как вы теряете некоторые встроенные модели поведения (например, вставки безопасной зоны), SwiftUI делает пользовательские панели вкладок простыми.

Репликация TabView с пользовательскими формами

Создайте пользовательский вид, используя кнопки, наложенные внизу. Используйте для выбора и переключения контента с вычисленным свойством или заявлением внутри .

struct CustomTabView: View {
 @State private var selection = 0

 var body: some View {
 VStack(spacing: 0) {
 // Content area
 Group {
 if selection == 0 {
 HomeView()
 } else if selection == 1 {
 SearchView()
 } else {
 ProfileView()
 }
 }
 .frame(maxWidth: .infinity, maxHeight: .infinity)

 // Custom tab bar
 HStack {
 TabBarButton(icon: "house.fill", label: "Home", isSelected: selection == 0)
 .onTapGesture { selection = 0 }
 Spacer()
 TabBarButton(icon: "magnifyingglass", label: "Search", isSelected: selection == 1)
 .onTapGesture { selection = 1 }
 Spacer()
 TabBarButton(icon: "person.fill", label: "Profile", isSelected: selection == 2)
 .onTapGesture { selection = 2 }
 }
 .padding(.horizontal)
 .padding(.top, 8)
 .background(Color(uiColor: .systemBackground)
 .shadow(radius: 2))
 }
 .ignoresSafeArea(.keyboard) // Prevent tab bar from moving with keyboard
 }
}

Анимационные переходы Tab

Добавить плавные переходы при переключении вкладок, обернув содержимое в блок . Например, добавить слайд или переход непрозрачности. Также можно анимировать сам значок, используя на iOS 17+ (например, отскоки или импульс).

Оптимизация производительности

Приложения с несколькими вкладками могут страдать от раздутия памяти, если вы загружаете все вкладки с нетерпением. TabView SwiftUI по умолчанию ленив: он только создает и отображает вид для выбранной вкладки. Однако, если вы используете тяжелые для состояния представления, вы все равно можете оптимизировать.

Lazy Loading Tab Content (альбом)

Используйте или внутри вкладок, чтобы отложить создание просмотра до прокрутки. Избегайте использования сложных вычислений в инициализаторе вкладок детских просмотров; вместо этого полагайтесь на , чтобы начать загрузку данных.

Избегать утечки памяти

Поскольку вкладки часто содержат ссылки на экземпляры , убедитесь, что вы не создаете ненужных сильных циклов ссылок. Используйте для принадлежащих объектов и для общих. Если вкладка никогда не показана, ее взгляд все еще может храниться в памяти — используйте (настраиваемая обертка) для отсрочки инициализации до выбора вкладки.

Тестирование вашего интерфейса Tabbed

Автоматизированные тесты помогают обеспечить правильную навигацию по вкладкам в разных состояниях устройства.

Тесты для выбора Tab

Поскольку представление вкладки управляется переменной , вы можете проверить логику, которая изменяет . Например, проверить, что глубокая ссылка правильно парсирует и присваивает индекс вкладки.

func testTabSelectionViaDeepLink() {
 let url = URL(string: "myapp://tab/2")!
 let tabNumber = extractTab(from: url)
 XCTAssertEqual(tabNumber, 2)
}

Тесты с использованием XCTest

Используйте XCUITest, чтобы убедиться, что нажатие на элемент вкладки отображает правильный контент. Доступ к элементам вкладки по их этикетке или идентификатору доступности.

func testTappingSearchTab() {
 let app = XCUIApplication()
 app.launch()

 let searchTab = app.tabBars.buttons["Search"]
 XCTAssertTrue(searchTab.exists)
 searchTab.tap()
 XCTAssertTrue(app.staticTexts["Find what you need"].exists)
}

Пример из реального мира: многотабовая панель управления

Давайте соберем все вместе с практическим примером: приложение для панели инструментов с тремя вкладками — Обзор, Аналитика и Настройки.

Обзор Tab

В прокрутке показано краткое изложение ключевых показателей (например, ежедневные активные пользователи, выручка). Используется , чтобы позволить бурение в подробные отчеты. Выбор вкладок сохраняется .

Аналитика Tab

Содержит диаграммы и графики, построенные с помощью Swift Charts. Поскольку рендеринг диаграмм может быть тяжелым, пометьте вид как и запустите загрузку данных только тогда, когда появится вкладка.

Настройки Tab

Отображает список переключателей, ссылок и кнопки выхода из системы. Эта вкладка может нуждаться в доступе к объекту среды, содержащему сеанс пользователя. Пропуск этого через гарантирует, что все вкладки обмениваются одними и теми же данными.

Общие подводные камни и решения

Даже опытные разработчики SwiftUI сталкиваются с несколькими повторяющимися проблемами с TabView .

Таб-бар исчез в навигационном стеке

Если вы нажимаете на навигационный стек внутри вкладки, панель вкладок может исчезнуть при нажатии. Решение: используйте (iOS 16+) вместо - панель вкладок остается видимой. Альтернативно, вручную спрячь / показать панель вкладок с помощью модификатора .

Неправильные значения тегов

Если вы не присвоите каждому ребенку, вид вкладки может не соответствовать правильному связыванию выбора. Всегда предоставляйте отдельный, хэшируемый тег для каждой вкладки.

Tab View Перезагрузка государства

Иногда при переключении вкладок состояние просмотра ребенка сбрасывается. Это по дизайну - SwiftUI создает новый экземпляр просмотра ребенка каждый раз, когда ему нужно отобразить его. Чтобы сохранить состояние через переключатели вкладок, поднимите состояние в родительский вид или используйте на уровне выше TabView . Альтернативно, используйте для общих данных, которые не должны сбрасываться.

Заключение

Проектирование многотабового интерфейса с SwiftUI является простым и настраиваемым. Компонент TabView дает вам прочную основу, и с несколькими дополнительными строками кода вы можете добавлять значки, управлять состоянием, интегрировать навигационные стеки и даже создавать полностью пользовательские панели вкладок. Следуя лучшим практикам для доступности, производительности и тестирования, вы создадите отполированный опыт навигации, который масштабируется на всех платформах Apple.

Продолжайте исследовать, погружаясь в официальную документацию Apple TabView , узнавая о руководящих принципах человеческого интерфейса для полос вкладок и экспериментируя с SF символами , чтобы придать вашим вкладкам отполированный, системно-согласованный вид.