Я постараюсь ответить на ваши вопросы один за другим. Я буду следовать небольшому примеру, где наше представление, которое должно быть многократно использовано, представляет собой простой View
, который показывает Text
и NavigationLink
, который будет go до некоторого Destination
. Я создал Gist: SwiftUI - Гибкая навигация с координаторами , если вы хотите взглянуть на мой полный пример.
Проблема разработки: ссылки NavigationLink жестко закодированы в View.
В вашем примере он привязан к представлению, но, как уже показывали другие ответы, вы можете добавить пункт назначения в свой тип представления struct MyView<Destination: View>: View
. Вы можете использовать любой тип, соответствующий View в качестве пункта назначения.
Но если представление, содержащее эту NavigationLink, должно быть многоразовым, я не могу жестко закодировать пункт назначения. Должен существовать механизм, обеспечивающий назначение.
С изменением, приведенным выше, существуют механизмы для предоставления типа. Один пример:
struct BoldTextView: View {
var text: String
var body: some View {
Text(text)
.bold()
}
}
struct NotReusableTextView: View {
var text: String
var body: some View {
VStack {
Text(text)
NavigationLink("Link", destination: BoldTextView(text: text))
}
}
}
изменится на
struct ReusableNavigationLinkTextView<Destination: View>: View {
var text: String
var destination: () -> Destination
var body: some View {
VStack {
Text(text)
NavigationLink("Link", destination: self.destination())
}
}
}
, и вы можете передать пункт назначения следующим образом:
struct BoldNavigationLink: View {
let text = "Text"
var body: some View {
ReusableNavigationLinkTextView(
text: self.text,
destination: { BoldTextView(text: self.text) }
)
}
}
Как только у меня появляется несколько экранов многократного использования, я сталкиваюсь с логической проблемой, что одному повторно используемому представлению (ViewA) требуется предварительно сконфигурированное представление-назначение (ViewB). Но что, если ViewB также требуется предварительно сконфигурированный View-destination View C? Мне нужно создать ViewB уже таким образом, чтобы View C вводился уже в ViewB, прежде чем я добавлю ViewB в ViewA. И так далее ...
Ну, очевидно, вам нужен какой-то лог c, который определит ваш Destination
. В какой-то момент вам нужно сообщить представлению, какое представление будет следующим. Я предполагаю, что вы пытаетесь избежать этого:
struct NestedMainView: View {
@State var text: String
var body: some View {
ReusableNavigationLinkTextView(
text: self.text,
destination: {
ReusableNavigationLinkTextView(
text: self.text,
destination: {
BoldTextView(text: self.text)
}
)
}
)
}
}
Я собрал простой пример, который использует Coordinator
s для передачи зависимостей и создания представлений. Существует протокол для Координатора, и вы можете реализовать определенные c варианты использования, основанные на этом.
protocol ReusableNavigationLinkTextViewCoordinator {
associatedtype Destination: View
var destination: () -> Destination { get }
func createView() -> ReusableNavigationLinkTextView<Destination>
}
Теперь мы можем создать определенный c Координатор, который будет отображать BoldTextView
при нажатии на NavigationLink
.
struct ReusableNavigationLinkShowBoldViewCoordinator: ReusableNavigationLinkTextViewCoordinator {
@Binding var text: String
var destination: () -> BoldTextView {
{ return BoldTextView(text: self.text) }
}
func createView() -> ReusableNavigationLinkTextView<Destination> {
return ReusableNavigationLinkTextView(text: self.text, destination: self.destination)
}
}
Если вы хотите, вы также можете использовать Coordinator
для реализации пользовательских логи c, которые определяют пункт назначения вашего представления. Следующий координатор показывает ItalicTextView
после четырех щелчков по ссылке.
struct ItalicTextView: View {
var text: String
var body: some View {
Text(text)
.italic()
}
}
struct ShowNavigationLinkUntilNumberGreaterFourThenItalicViewCoordinator: ReusableNavigationLinkTextViewCoordinator {
@Binding var text: String
let number: Int
private var isNumberGreaterThan4: Bool {
return number > 4
}
var destination: () -> AnyView {
{
if self.isNumberGreaterThan4 {
let coordinator = ItalicTextViewCoordinator(text: self.text)
return AnyView(
coordinator.createView()
)
} else {
let coordinator = ShowNavigationLinkUntilNumberGreaterFourThenItalicViewCoordinator(
text: self.$text,
number: self.number + 1
)
return AnyView(coordinator.createView())
}
}
}
func createView() -> ReusableNavigationLinkTextView<AnyView> {
return ReusableNavigationLinkTextView(text: self.text, destination: self.destination)
}
}
Если у вас есть данные, которые необходимо передать, создайте другого координатора вокруг другого координатора для хранения значения. В этом примере у меня есть TextField
-> EmptyView
-> Text
, где значение из TextField должно быть передано Text.
. EmptyView
не должно иметь эту информацию.
struct TextFieldView<Destination: View>: View {
@Binding var text: String
var destination: () -> Destination
var body: some View {
VStack {
TextField("Text", text: self.$text)
NavigationLink("Next", destination: self.destination())
}
}
}
struct EmptyNavigationLinkView<Destination: View>: View {
var destination: () -> Destination
var body: some View {
NavigationLink("Next", destination: self.destination())
}
}
Это координатор, который создает представления путем вызова других координаторов (или создает сами представления). Он передает значение от TextField
до Text
, а EmptyView
не знает об этом.
struct TextFieldEmptyReusableViewCoordinator {
@Binding var text: String
func createView() -> some View {
let reusableViewBoldCoordinator = ReusableNavigationLinkShowBoldViewCoordinator(text: self.$text)
let reusableView = reusableViewBoldCoordinator.createView()
let emptyView = EmptyNavigationLinkView(destination: { reusableView })
let textField = TextFieldView(text: self.$text, destination: { emptyView })
return textField
}
}
Чтобы обернуть все это, вы также можете создать MainView
, который имеет некоторые logi c, который решает, какой View
/ Coordinator
следует использовать.
struct MainView: View {
@State var text = "Main"
var body: some View {
NavigationView {
VStack(spacing: 32) {
NavigationLink("Bold", destination: self.reuseThenBoldChild())
NavigationLink("Reuse then Italic", destination: self.reuseThenItalicChild())
NavigationLink("Greater Four", destination: self.numberGreaterFourChild())
NavigationLink("Text Field", destination: self.textField())
}
}
}
func reuseThenBoldChild() -> some View {
let coordinator = ReusableNavigationLinkShowBoldViewCoordinator(text: self.$text)
return coordinator.createView()
}
func reuseThenItalicChild() -> some View {
let coordinator = ReusableNavigationLinkShowItalicViewCoordinator(text: self.$text)
return coordinator.createView()
}
func numberGreaterFourChild() -> some View {
let coordinator = ShowNavigationLinkUntilNumberGreaterFourThenItalicViewCoordinator(text: self.$text, number: 1)
return coordinator.createView()
}
func textField() -> some View {
let coordinator = TextFieldEmptyReusableViewCoordinator(text: self.$text)
return coordinator.createView()
}
}
Я знаю, что мог бы также создать протокол Coordinator
и некоторые базовые методы, но я хотел показать простой пример того, как с ними работать.
Кстати, это очень похоже на то, как я использовал Coordinator
в приложениях Swift UIKit
.
Если у вас есть какие-либо вопросы , отзывы или вещи, чтобы улучшить его, дайте мне знать.