SwiftUI MVVM Координатор / Маршрутизатор / NavigationLink - PullRequest
2 голосов
/ 13 апреля 2020

У меня проблемы с переводом шаблонов архитектуры UIKit в SwiftUI. Моя текущая модель в основном MVVM с координаторами / маршрутизаторами. Часть MVVM кажется довольно простой и естественной с добавлением @ ObservableObject / @ Опубликовано. Но координация / маршрутизация кажется не интуитивной. Функции просмотра и координации (навигации) тесно связаны в SwiftUI. Кажется, что на самом деле невозможно отделить их от использования вспомогательной структуры AnyView.

Вот один пример: я хочу создать повторно используемую строку / ячейку в SwiftUI. Допустим, этот ряд в Production довольно сложный, поэтому я хочу использовать его повторно. Я хочу поместить его также в другой модуль, чтобы я мог повторно использовать его в нескольких целях. (например, iOS, macCatalyst, et c ...)

enter image description here

Теперь я хочу контролировать, что происходит, когда пользователь нажимает на это вид или кнопки в этом представлении. В зависимости от контекста мне нужно ориентироваться в разных направлениях. Насколько я понимаю, возможные цели NavigationLink должны быть либо аппаратно в представлении, либо AnyView должны быть переданы в представление.

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

struct ProductFamilyRow: View {
    @State private var selection: Int? = 0
    let item: ProductFamilyItem

    let destinationView1: AnyView
    let destinationView2: AnyView

    var body: some View {
        VStack {
            NavigationLink(
                destination: destinationView1,
                tag: 1,
                selection: self.$selection
            ) {
                EmptyView()
            }

            NavigationLink(
                destination: destinationView2,
                tag: 2,
                selection: self.$selection
            ) {
                EmptyView()
            }

            HStack {
                Text(item.title)
                Button("Destination 1") {
                    self.selection = 1
                }.foregroundColor(Color.blue)

                Button("Destination 2") {
                    self.selection = 2
                }.foregroundColor(Color.blue)
            }

            //Image(item.image)
        }.buttonStyle(PlainButtonStyle())
    }
}

Это, кажется, главный недостаток дизайна в SwiftUI. Повторно используемые компоненты с навигационными ссылками в принципе невозможны, кроме использования хака AnyView. Насколько я знаю, AnyView просто используется для конкретных c случаев, когда мне нужно стирание типа и у него есть некоторые недостатки производительности. Поэтому я не считаю это решением idiomati c для создания многократно используемых навигационных представлений с помощью SwiftUI.

Это действительно единственное решение? Может быть, я совершенно не прав, и это в любом случае неправильное направление. Я где-то читал (больше не могу найти пост ...) об использовании какого-то центрального состояния, которое указывает, какой вид показывать, но я не видел конкретного примера, как это сделать.

2-й вызов: Также я не хочу, чтобы ячейка реагировала на любые другие нажатия, кроме кнопок. Но, кажется, невозможно контролировать, куда перемещается ячейка, если нажать. (не нажимая ни на одну из кнопок, но в любом месте ячейки) В текущем примере кода он (по любой причине) перемещается к «Пункту назначения 2».

Заранее спасибо.

1 Ответ

2 голосов
/ 13 апреля 2020

Лучше использовать генерики для вашей строки, как показано ниже (протестировано с Xcode 11.4)

Пример использования:

ProductFamilyRow(item: ProductFamilyItem(title: "Test"),
    destinationView1: { Text("Details1") },
    destinationView2: { Text("Details2") })

Интерфейс:

Обновление - добавлен блок подсветки строк. Список имеет автоопределение для кнопки или ссылки внутри строки и подсвечивает, если присутствует какой-либо стандартный (! Ключ). Таким образом, чтобы отключить такое поведение, нужно скрыть все под собственным стилем кнопок.

struct ProductFamilyRowStyle: ButtonStyle {

    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .colorMultiply(configuration.isPressed ? 
                 Color.white.opacity(0.5) : Color.white) // any effect you want
    }
}

struct ProductFamilyRow<D1: View, D2: View>: View {
    let item: ProductFamilyItem
    let destinationView1: () -> D1
    let destinationView2: () -> D2

    init(item: ProductFamilyItem, @ViewBuilder destinationView1: @escaping () -> D1,
        @ViewBuilder destinationView2: @escaping () -> D2)
    {
        self.item = item
        self.destinationView1 = destinationView1
        self.destinationView2 = destinationView2
    }

    @State private var selection: Int? = 0

    var body: some View {
        VStack {
            HStack {
                Text(item.title)
                Button(action: {
                    self.selection = 1
                }) {
                    Text("Destination 1")
                        .background( // hide link inside button !!
                            NavigationLink(destination: destinationView1(),
                                tag: 1, selection: self.$selection) { EmptyView() }
                        )
                }.foregroundColor(Color.blue)

                Button(action: {
                    self.selection = 2
                }) {
                    Text("Destination 2")
                        .background(
                            NavigationLink(destination: destinationView2(),
                                tag: 2, selection: self.$selection) { EmptyView() }
                        )
                }.foregroundColor(Color.blue)
            }

            //Image(item.image)
        }.frame(maxWidth: .infinity) // to have container centered
        .buttonStyle(ProductFamilyRowStyle())
    }
}
...