SwiftUI имеет совершенно новые API-интерфейсы навигации в iOS 16 и macOS 13. Они позволяют нам определять навигацию на основе стека и навигацию по нескольким столбцам.

#NavigationStack

Навигация на основе стека состоит из корневого вью, у которого могут быть дополнительные вью, “накладываемые” поверх него, тем самым создавая стек. Обычно мы используем этот тип навигации для приложений, работающих на iPhone.

Допустим, мы хотели бы иметь приложение, которое показывает группы коренных животных Новой Зеландии. Мы можем кликнуть по группе в списке и она перенесет нас на соответствующую страницу с информацией о животных в группе.

Чтобы определить такую навигацию, мы начинаем с NavigationStack и вью List внутри него. Каждая строка в List представляет собой NavigationLink, которая помещает встроенное вью “на” стек.

NavigationStack {
    List(groups) { group in
        NavigationLink(group.name) {
            AnimalGroupView(group: group)
        }
    }
}

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

Вы можете найти полный пример кода для NavigationStack на GitHub.

#Программная навигация в NavigationStack

 Если мы хотим внедрить программную навигацию, нам сначала нужно обратиться к другому типу ссылок. Вместо встроенного вью, мы можем определить ссылки, которые будут помещать данные в стек. Затем для ссылок внутри модификатора navigationDestination() объявляются целевые (*конечные) вью.

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

NavigationStack {
    List(groups) { group in
        NavigationLink(group.name, value: group.id)
    }
    .navigationDestination(
        for: AnimalGroup.ID.self
    ) { groupId in
        AnimalGroupView(groupId: groupId)
    }
}

SwiftUI использует тип представленного значения для выбора соответствующего целевого вью. В нашем случае у нас есть только один тип назначения, который принимает один тип данных AnimalGroup.ID, но мы могли бы добавить больше модификаторов navigationDestination(), если бы у нас было больше типов назначения.

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

NavigationStack может принимать привязки двух типов: Collection или NavigationPath.

Проще всего работать с путем Array, его можно использовать для однородного состояния навигации, где все элементы одного типа. Вы можете увидеть, какие другие типы коллекций принимаются, просмотрев протоколы, которым должен соответствовать путь в инициализаторе NavigationStack.

Данные, которые navigation links помещают в стек, данные, представленные в navigation destination, и элементы в navigation path должны быть одного типа.

Мы используем AnimalGroup.ID в качестве типа данных для отправки в стек и в качестве элементов пути. Здесь мы можем программно управлять путем, указав для него значение по умолчанию.

struct AnimalGroupsView: View {
    var groups: [AnimalGroup]
    
    @State private var path: [AnimalGroup.ID] = ["Birds"]
    
    var body: some View {
        NavigationStack(path: $path) {
            List(groups) { group in
                NavigationLink(group.name, value: group.id)
            }
            .navigationDestination(
                for: AnimalGroup.ID.self
            ) { groupId in
                AnimalGroupView(groupId: groupId)
            }
        }
    }
}

Мы также можем манипулировать состоянием навигации, добавляя элементы в путь Array или удаляя из него элементы.

Обратите внимание, что мы должны добавить модификатор navigationDestination() как можно выше в нашей иерархии, чтобы он уже обрабатывался SwiftUI, когда мы манипулируем состоянием навигации. В противном случае SwiftUI не сможет сопоставить destinations ( точки назначения ) с элементами пути. Например, если бы у нас был длинный список групп,  который пользователям нужно было бы прокручивать, чтобы увидеть их все, и мы прикрепили navigationDestination() к одной из скрытых строк, то SwiftUI смог бы правильно сопоставить destination только тогда, когда эта строка cтала бы видимой на экране.

Вы можете найти пример кода для программного NavigationStack на GitHub.

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

#NavigationSplitView

 Когда мы хотим, чтобы наше приложение поддерживало iPad или Mac, мы должны рассмотреть возможность реализации многоколоночной (многоколонной) навигации. В этом типе навигации у нас обычно есть боковая панель слева, где пользователь может выбирать элементы, которые будут отображаться в подробном виде справа.

Чтобы определить многоколоночную навигацию с помощью новых API-интерфейсов SwiftUI, мы используем NavigationSplitView.

Для навигации в NavigationSplitView мы опираемся на привязку selection из вью List на боковой панели. Целевые вью определяются в detail конструкторе вью в NavigationSplitView.

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

struct AnimalGroupsView: View {
    var groups: [AnimalGroup]
        
    @State private var selection: Set<AnimalGroup.ID> = []
    
    var body: some View {
        NavigationSplitView {
            List(groups, selection: $selection) { group in
                Text(group.name)
            }
        } detail: {
            AnimalGroupsDetail(groupIds: selection)
        }
    }
}

Мы всегда контролируем состояние NavigationSplitView, поэтому он всегда поддерживает программную навигацию. Мы можем манипулировать состоянием выбора, и оно будет отражено на боковой панели и в подробном вью.

NavigationSplitView автоматически адаптируется к навигационному стеку на iPhone и компактному (сжатому) типу горизонтального размера на iPad. Я заметила, что, хотя нам не нужно помещать навигационные ссылки в строки списка и только использовать простые Text вью, как это показано в примерах документации NavigationSplitView, он не будет очищать выбранный фон из строк, когда пользователь нажимает кнопку «Назад» на iPhone.

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

Вы можете найти пример кода для NavigationSplitView на GitHub.

#Три колонки в NavigationSplitView

До сих пор мы рассматривали только две колонки в NavigationSplitView: боковую панель и подробный вид. Но мы также можем показать и три, если нам нужно.

Например, в нашем приложении для животных Новой Зеландии у нас может быть список групп в боковой панели, список животных во второй колонке и информация о животном в третьей.

Для поддержки трехколоночной навигации мы используем NavigationSplitView с content вью для второй колонки и detail вью для третьей колонки.

struct AnimalGroupsView: View {
    var groups: [AnimalGroup]
        
    @State private var groupIds: Set<AnimalGroup.ID> = []
    @State private var animalIds: Set<Animal.ID> = []
    
    var body: some View {
        NavigationSplitView {
            List(groups, selection: $groupIds) { group in
                Text(group.name)
            }
        } content: {
            AnimalGroupsDetail(groupIds: groupIds, animalIds: $animalIds)
        } detail: {
            AnimalsDetail(animalIds: animalIds)
        }
    }
}

Я попыталась следовать примеру из документации NavigationSplitView с помощью if let внутри content вью, но у меня это не сработало. Выбор никогда не обновлялся. Позже я увидела, что это известная проблема, упомянутая в примечаниях к выпуску SwiftUI. Я сделала пример с множественным выбором как в колонке боковой панели, так и в колонке контента, чтобы избежать этой проблемы, но в примечаниях к выпуску также упоминается, что в качестве обходного пути мы можем обернуть содержимое колонки в ZStack, если хотим использовать условные операторы.

Вы можете найти пример кода для NavigationSplitView с тремя колонками на GitHub.

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

Все примеры кода для этого поста доступны на GitHub.

Похожие сообщения:

  • Программно скрыть и показать боковую панель в разделенном представлении.

  • Использование действия отклонения из среды SwiftUI.

Если вам нравится наш блог и вы хотите поддержать нас, вы можете спонсировать нас на GitHub.

Чтобы получать новости о блоге и советы по разработке, следите за нами в Твиттере @nilcoalescing

Оригинал статьи

Подписывайся на наши соцсети: Telegram / VKontakte
Вступай в открытый чат для iOS-разработчиков: 
t.me/swiftbook_chat
Смотри 
бесплатные уроки по iOS-разработке с нуля

Комментарии (0)