Я пытаюсь сделать архитектуру для более крупного, готового к использованию приложения SwiftUI. Я все время сталкиваюсь с одной и той же проблемой, которая указывает на серьезный недостаток дизайна в SwiftUI.
Еще никто не мог дать мне полный рабочий, готовый к производству ответ.
Как сделать многоразовые представления, в SwiftUI
которых содержится навигация?
Поскольку SwiftUI
NavigationLink
он сильно привязан к представлению, это просто невозможно, чтобы оно масштабировалось и в более крупных приложениях. NavigationLink
Да, в этих небольших примерах приложения работают, но не сразу, если вы хотите повторно использовать несколько представлений в одном приложении. И, возможно, также повторно использовать границы модуля. (например, повторное использование View в iOS, WatchOS и т. д.)
Проблема дизайна: ссылки NavigationLink жестко закодированы в View.
NavigationLink(destination: MyCustomView(item: item))
Но если представление, содержащее это, NavigationLink
должно быть повторно использовано, я не могу жестко закодировать место назначения. Должен быть механизм, который обеспечивает пункт назначения. Я спросил это здесь и получил довольно хороший ответ, но все еще не полный ответ:
SwiftUI MVVM Координатор / Маршрутизатор / NavigationLink
Идея заключалась в том, чтобы добавить целевые ссылки в многоразовое представление. Обычно идея работает, но, к сожалению, она не масштабируется для реальных производственных приложений. Как только у меня появляется несколько экранов многократного использования, я сталкиваюсь с логической проблемой, что одному повторно используемому представлению ( ViewA
) требуется предварительно сконфигурированный view-destination ( ViewB
). Но что, если ViewB
также необходимо предварительно сконфигурированное представление-назначение ViewC
? Мне нужно было создать ViewB
уже таким образом , что ViewC
впрыскивается уже ViewB
перед тем, как вводить ViewB
в ViewA
. И так далее ... но поскольку данные, которые должны быть переданы в это время, недоступны, вся конструкция не работает.
Еще одна идея, которая у меня была, заключалась в том, чтобы использовать Environment
механизм внедрения зависимостей для внедрения мест назначения NavigationLink
. Но я думаю, что это следует рассматривать более или менее как взлом, а не масштабируемое решение для больших приложений. В конечном итоге мы бы использовали Среду в основном для всего. Но поскольку Environment также можно использовать только внутри View (не в отдельных координаторах или ViewModels), это, на мой взгляд, снова приведет к созданию странных конструкций.
Как бизнес-логика (например, код модели представления) и представление должны быть разделены, так и навигация и представление должны быть разделены (например, шаблон Координатора). UIKit
Это возможно, потому что мы имеем доступ к представлению UIViewController
и UINavigationController
за ним. UIKit's
У MVC уже была проблема, заключающаяся в том, что он смешал так много понятий, что стал забавным названием «Massive-View-Controller» вместо «Model-View-Controller». Сейчас похожая проблема сохраняется, SwiftUI
но, на мой взгляд, еще хуже. Навигация и Виды сильно связаны и не могут быть отделены. Поэтому невозможно сделать повторно используемые представления, если они содержат навигацию. Это можно было решить, UIKit
но сейчас я не вижу разумного решения вSwiftUI
, К сожалению, Apple не предоставила нам объяснения, как решать подобные архитектурные проблемы. Мы получили только несколько небольших примеров приложений.
Я хотел бы быть доказанным неправым. Пожалуйста, покажите мне чистый шаблон дизайна приложения, который решает эту проблему для готовых приложений.
Заранее спасибо.
Обновление: эта награда закончится через несколько минут, и, к сожалению, до сих пор никто не смог привести рабочий пример. Но я начну новую награду, чтобы решить эту проблему, если я не могу найти другое решение и связать его здесь. Спасибо всем за их большой вклад!
Ответы:
Закрытие это все, что вам нужно!
Я написал пост о замене шаблона делегата в SwiftUI на замыкания. https://swiftwithmajid.com/2019/11/06/the-power-of-closures-in-swiftui/
источник
Моей идеей было бы сочетание
Coordinator
иDelegate
шаблон. Сначала создайтеCoordinator
класс:Адаптируйте
SceneDelegate
для использованияCoordinator
:Внутри у
ContentView
нас есть это:Мы можем определить
ContenViewDelegate
протокол следующим образом:Где
Item
просто структура, которая может быть идентифицирована, может быть чем-то еще (например, id некоторого элемента, как в aTableView
в UIKit)Следующий шаг - принять этот протокол
Coordinator
и просто передать представление, которое вы хотите представить:До сих пор это хорошо работало в моих приложениях. Я надеюсь, что это помогает.
источник
Text("Returned Destination1")
что-то вродеMyCustomView(item: ItemType, destinationView: View)
. Так что для этогоMyCustomView
также необходимо ввести некоторые данные и пункт назначения. Как бы вы решили это?dependencies
иdestination
.Text("Returned Destination1")
. Что делать, если это должно бытьMyCustomView(item: ItemType, destinationView: View)
. Что ты собираешься делать там? Я понимаю внедрение зависимостей, слабую связь через протоколы и общие зависимости с координаторами. Все это не проблема - это необходимое вложение. Спасибо.Что-то, что приходит мне в голову, - это когда ты говоришь:
это не совсем правда. Вместо того, чтобы предоставлять представления, вы можете спроектировать повторно используемые компоненты так, чтобы вы поставляли крышки, которые предоставляют представления по требованию.
Таким образом, замыкание, которое создает ViewB по требованию, может предоставить ему замыкание, которое создает ViewC по требованию, но фактическое построение представлений может произойти в тот момент, когда доступна контекстная информация, которая вам нужна.
источник
Вот забавный пример бесконечной детализации и программного изменения данных для следующего подробного просмотра.
источник
Я пишу серию публикаций в блоге о создании подхода MVP + Coordinators в SwiftUI, который может быть полезен:
https://lascorbe.com/posts/2020-04-27-MVPCoordinators-SwiftUI-part1/
Полный проект доступен на Github: https://github.com/Lascorbe/SwiftUI-MVP-Coordinator.
Я пытаюсь сделать это так, как если бы это было большое приложение с точки зрения масштабируемости. Я думаю, что я разобрался с проблемой навигации, но мне все еще нужно посмотреть, как сделать глубокое связывание, над чем я сейчас работаю. Я надеюсь, что это помогает.
источник
NavigationView
рут-вид фантастическая. На данный момент это самая продвинутая реализация Координаторов SwiftUI, которую я когда-либо видел.NavigationLink
но делает это, вводя новую связанную зависимость. ВMasterView
вашем примере не зависит отNavigationButton
. Представьте себе размещениеMasterView
в Swift-пакете - он больше не будет компилироваться, потому что типNavigationButton
неизвестен. Также не вижу, какViews
бы этим была решена проблема вложенного многоразового использования ?Это совершенно необычный ответ, так что, вероятно, это будет чепуха, но я бы соблазнился использовать гибридный подход.
Используйте среду для прохождения через один объект-координатор - давайте назовем его NavigationCoordinator.
Дайте вашим повторно используемым представлениям какой-то идентификатор, который устанавливается динамически. Этот идентификатор дает семантическую информацию, соответствующую фактическому сценарию использования клиентского приложения и иерархии навигации.
Попросите повторно используемые представления запросить у NavigationCoordinator представление назначения, передав их идентификатор и идентификатор типа представления, к которому они перемещаются.
Это оставляет NavigationCoordinator в качестве единой точки внедрения, и это объект без просмотра, доступ к которому можно получить за пределами иерархии представления.
Во время установки вы можете зарегистрировать правильные классы представления для его возвращения, используя какое-то соответствие с идентификаторами, которые были переданы во время выполнения. В некоторых случаях может сработать что-то простое, например сопоставление с идентификатором назначения. Или сопоставление с парой идентификаторов хоста и назначения.
В более сложных случаях вы можете написать собственный контроллер, который учитывает другую специфическую для приложения информацию.
Поскольку он внедряется через среду, любое представление может переопределить NavigationCoordinator по умолчанию в любой точке и предоставить другое для своих подпредставлений.
источник