Создайте боковую панель SwiftUI

Я хочу создать очень простую боковую панель iOS 14 с помощью SwiftUI. Настройка довольно проста, у меня есть три представления HomeView, LibraryView и SettingsView и перечисление, представляющее каждый экран.

enum Screen: Hashable {
   case home, library, settings
}

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

Глобальное состояние принадлежит MainNavigationView, который также является корневым представлением для моего WindowGroup.

struct MainNavigationView: View {
    @State var screen: Screen? = .home
   
    var body: some View {
        NavigationView {
            SidebarView(state: $screen)
        }
        .navigationViewStyle(DoubleColumnNavigationViewStyle())
    }
}

SidebarView - это простой List, содержащий три NavigationLink, по одному на каждый Screen.

struct SidebarView: View {
    @Binding var state: Screen?
    var body: some View {
        List {
            NavigationLink(
                destination: HomeView(),
                tag: Screen.home,
                selection: $state,
                label: {
                    Label("Home", systemImage: "house" )
                })
            NavigationLink(
                destination: LibraryView(),
                tag: Screen.library,
                selection: $state,
                label: {
                    Label("Library", systemImage: "book")
                })
            NavigationLink(
                destination: SettingsView(),
                tag: Screen.settings,
                selection: $state,
                label: {
                    Label("Settings", systemImage: "gearshape")
                })
        }
        .listStyle(SidebarListStyle())
        .navigationTitle("Sidebar")
    
    }
}

Я использую инициализатор NavigationLink(destination:tag:selection:label), чтобы выбранный экран был установлен в моем MainNavigationView, чтобы я мог повторно использовать его для своего TabView позже.

Однако многие вещи работают не так, как ожидалось.

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

«Первая

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

«Переключение

Это всего лишь SwiftUI ошибки или есть другой способ создать боковую панель с Binding?


person jlsiewert    schedule 06.07.2020    source источник
comment
Кажется, это та же проблема, что описана здесь   -  person jlsiewert    schedule 06.07.2020


Ответы (1)


Вам необходимо включить вторичный вид по умолчанию в NavigationView { }, обычно это будет заполнитель, но вы можете использовать HomeScreen, например

struct MainNavigationView: View {
    @State var screen: Screen? = .home
   
    var body: some View {
        NavigationView {
            SidebarView(state: $screen)
            HomeScreen()
        }
        .navigationViewStyle(DoubleColumnNavigationViewStyle())
    }
}

Относительно того, что ячейка не выбирается повторно - начиная с iOS 14.2 нет привязки выбора списка (когда не в режиме редактирования), поэтому выбор теряется. Хотя у List API есть параметр $selection, в настоящее время он поддерживается только в macOS. Вы можете увидеть эту информацию в заголовке:

/// On iOS and tvOS, you must explicitly put the list into edit mode for
/// the selection to apply.

Это немного запутано, но это означает, что привязка выбора, которая нам нужна для боковой панели, предназначена только для macOS, в iOS она предназначена только для множественного выбора (т.е. галочки) в режиме редактирования. Причина может быть в том, что выбор UITableView управляется событиями, возможно, это не удалось перевести в управляемую состоянием природу SwiftUI. Если вы когда-либо пытались выполнить восстановление состояния с представлением, которое уже было загружено на навигационный контроллер, и попытались показать анимацию невыделения ячейки при возвращении, и это представление таблицы не было загружено, и ячейка никогда не выделялась, в первую очередь, вы знаешь что я имею ввиду. Было кошмаром синхронно загрузить таблицу, нарисовать выбранную ячейку и затем запустить анимацию невыделения. Я ожидаю, что Apple будет повторно реализовывать List, Sidebar и NavigationView в чистом SwiftUI, чтобы преодолеть эти проблемы, так что пока нам просто нужно с этим жить.

Как только это будет исправлено, все будет так просто, как List(selection:$screen) { }, как это будет работать в macOS. В качестве обходного пути в iOS вы можете вместо этого выделить значок или текст по-своему, например попробуйте использовать жирный текст:

    NavigationLink(
        destination: HomeView(),
        tag: Screen.home,
        selection: $state,
        label: {
            Label("Home", systemImage: "house" )
        })
        .font(Font.headline.weight(state == Screen.home ? .bold : .regular))

введите описание изображения здесь

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

Есть еще 2 ошибки, о которых вам следует знать:

  1. В портретной ориентации боковая панель отображается только при втором нажатии кнопки навигации боковой панели.
  2. В портретной ориентации, если вы показываете боковую панель и выбираете тот же элемент, который уже отображается, боковая панель не закрывается.
person malhal    schedule 25.10.2020
comment
Спасибо за подробный ответ, хотя это просто похоже на ошибку. Я опубликую здесь обновление после того, как опробую некоторые из этих идей. - person jlsiewert; 28.10.2020