Staram się stworzyć architekturę dla większej, gotowej do produkcji aplikacji SwiftUI. Cały czas pracuję nad tym samym problemem, który wskazuje na poważną wadę projektową w SwiftUI.
Nadal nikt nie dał mi pełnej odpowiedzi, gotowej do produkcji.
Jak zrobić widoki wielokrotnego użytku, w SwiftUI
których znajduje się nawigacja?
Ponieważ SwiftUI
NavigationLink
jest to ściśle związane z widokiem, po prostu nie jest to możliwe w taki sposób, że skaluje się również w większych aplikacjach. NavigationLink
w tych małych próbkach działają aplikacje - tak, ale nie tak szybko, jak chcesz ponownie użyć wielu widoków w jednej aplikacji. A może też ponownie wykorzystaj granice modułów. (jak: ponowne użycie Wyświetl w iOS, WatchOS itp.)
Problem projektowy: łącza NavigationLink są zakodowane na stałe w widoku.
NavigationLink(destination: MyCustomView(item: item))
Ale jeśli widok zawierający to NavigationLink
powinno być wielokrotnego użytku, nie mogę zakodować miejsca docelowego. Musi istnieć mechanizm zapewniający miejsce docelowe. Zapytałem o to tutaj i otrzymałem całkiem dobrą odpowiedź, ale wciąż nie pełną odpowiedź:
SwiftUI MVVM Koordynator / Router / NavigationLink
Pomysł polegał na wprowadzeniu linków docelowych do widoku wielokrotnego użytku. Ogólnie pomysł działa, ale niestety nie można go skalować do prawdziwych aplikacji produkcyjnych. Gdy tylko mam wiele ekranów wielokrotnego użytku, napotykam logiczny problem, że jeden widok wielokrotnego użytku ( ViewA
) potrzebuje wstępnie skonfigurowanego miejsca docelowego widoku ( ViewB
). Ale co, jeśli ViewB
potrzebuje również wstępnie skonfigurowanego miejsca docelowego widoku ViewC
? Musiałbym tworzyć ViewB
już w taki sposób, który ViewC
jest wtryskiwany już ViewB
przed I wstrzyknąć ViewB
do ViewA
. I tak dalej .... ale ponieważ dane, które w tym czasie muszą zostać przekazane, nie są dostępne, cała konstrukcja zawodzi.
Innym pomysłem było użycie Environment
mechanizmu wstrzykiwania zależności do wstrzykiwania miejsc docelowych NavigationLink
. Myślę jednak, że należy to traktować mniej więcej jako hack, a nie skalowalne rozwiązanie dla dużych aplikacji. Skończylibyśmy na użyciu środowiska w zasadzie do wszystkiego. Ale ponieważ Środowiska można również używać tylko wewnątrz View (nie w osobnych Koordynatorach ani ViewModels), to moim zdaniem ponownie stworzy dziwne konstrukcje.
Podobnie jak logika biznesowa (np. Kod modelu widoku) i widok muszą być oddzielone, również nawigacja i widok muszą być oddzielone (np. Wzorzec koordynatora) UIKit
Jest to możliwe, ponieważ mamy dostęp do widoku UIViewController
i UINavigationController
za nim. UIKit's
MVC miał już problem, że zebrał tak wiele koncepcji, że stał się zabawną nazwą „Massive-View-Controller” zamiast „Model-View-Controller”. Teraz podobny problem utrzymuje się, SwiftUI
ale jeszcze gorzej, moim zdaniem. Nawigacja i widoki są silnie powiązane i nie można ich rozdzielić. Dlatego nie można tworzyć widoków wielokrotnego użytku, jeśli zawierają nawigację. Możliwe było rozwiązanie tego problemu, UIKit
ale teraz nie widzę rozsądnego rozwiązaniaSwiftUI
. Niestety Apple nie wyjaśniło nam, jak rozwiązać takie problemy architektoniczne. Mamy tylko kilka przykładowych aplikacji.
Chciałbym, aby udowodniono, że się mylę. Pokaż mi czysty wzór projektowania aplikacji, który rozwiązuje ten problem w przypadku dużych aplikacji gotowych do produkcji.
Z góry dziękuję.
Aktualizacja: ta nagroda skończy się za kilka minut i niestety nadal nikt nie był w stanie podać działającego przykładu. Ale zacznę nową nagrodę, aby rozwiązać ten problem, jeśli nie mogę znaleźć innego rozwiązania i połączyć go tutaj. Dziękujemy wszystkim za ich wspaniały wkład!
Odpowiedzi:
Zamknięcie to wszystko czego potrzebujesz!
Napisałem post o zamianie wzoru delegowanego w SwiftUI na zamknięcia. https://swiftwithmajid.com/2019/11/06/the-power-of-closures-in-swiftui/
źródło
Moim pomysłem byłoby połączenie
Coordinator
iDelegate
wzór. Najpierw utwórzCoordinator
klasę:Dostosuj
SceneDelegate
do użyciaCoordinator
:Wewnątrz
ContentView
mamy to:Możemy zdefiniować
ContenViewDelegate
protokół w następujący sposób:Gdzie
Item
jest tylko struktura, która jest możliwa do zidentyfikowania, może to być cokolwiek innego (np. Identyfikator jakiegoś elementu, np.TableView
W UIKit)Następnym krokiem jest przyjęcie tego protokołu
Coordinator
i po prostu przekazanie widoku, który chcesz przedstawić:Do tej pory działało to dobrze w moich aplikacjach. Mam nadzieję, że to pomoże.
źródło
Text("Returned Destination1")
na coś podobnegoMyCustomView(item: ItemType, destinationView: View)
. ToMyCustomView
także wymaga podania niektórych danych i miejsca docelowego. Jak byś to rozwiązał?dependencies
idestination
.Text("Returned Destination1")
. Co jeśli to musi byćMyCustomView(item: ItemType, destinationView: View)
. Co tam zamierzasz wstrzyknąć? Rozumiem wstrzykiwanie zależności, luźne sprzęganie przez protokoły i wspólne zależności z koordynatorami. Wszystko to nie stanowi problemu - to potrzebne zagnieżdżanie. Dzięki.Coś, co przychodzi mi do głowy, to to, że kiedy mówisz:
to nie do końca prawda. Zamiast dostarczać widoki, możesz zaprojektować komponenty wielokrotnego użytku, aby dostarczyć zamknięcia, które dostarczają widoki na żądanie.
W ten sposób zamknięcie, które produkuje ViewB na żądanie, może zapewnić zamknięcie, które produkuje ViewC na żądanie, ale faktyczna konstrukcja widoków może się zdarzyć w czasie, gdy potrzebne są potrzebne informacje kontekstowe.
źródło
Oto zabawny przykład wiercenia w nieskończoność i programowej zmiany danych do następnego widoku szczegółów
źródło
Piszę serię postów na blogu o tworzeniu podejścia MVP + Coordinators w SwiftUI, które może być przydatne:
https://lascorbe.com/posts/2020-04-27-MVPCoordinators-SwiftUI-part1/
Pełny projekt jest dostępny na Github: https://github.com/Lascorbe/SwiftUI-MVP-Coordinator
Próbuję to zrobić, jakby była to duża aplikacja pod względem skalowalności. Wydaje mi się, że rozwiązałem problem z nawigacją, ale wciąż muszę zobaczyć, jak wykonać głębokie linkowanie, nad czym obecnie pracuję. Mam nadzieję, że to pomoże.
źródło
NavigationView
widoku głównego jest fantastyczny. To zdecydowanie najbardziej zaawansowana implementacja SwiftUI Coordinators, jaką do tej pory widziałem.NavigationLink
ale robi to poprzez wprowadzenie nowej zależności sprzężonej. WMasterView
twoim przykładzie nie zależy odNavigationButton
. Wyobraź sobie umieszczenieMasterView
w Szybkim Pakiecie - nie będzie się już kompilować, ponieważ ten typNavigationButton
jest nieznany. Nie rozumiem też, w jaki sposób możnaViews
by przez to rozwiązać problem zagnieżdżonego wielokrotnego użytku ?To jest całkowicie nierealna odpowiedź, więc prawdopodobnie okaże się nonsensem, ale skusiłbym się na podejście hybrydowe.
Użyj środowiska, aby przejść przez pojedynczy obiekt koordynatora - nazwijmy go NavigationCoordinator.
Daj swoim widokom wielokrotnego użytku jakiś identyfikator, który jest ustawiany dynamicznie. Ten identyfikator podaje informacje semantyczne odpowiadające rzeczywistemu przypadkowi użycia aplikacji i hierarchii nawigacji.
Poproś, aby widoki wielokrotnego użytku wysłały zapytanie do NavigationCoordinator o widok docelowy, przekazując swój identyfikator i identyfikator typu widoku, do którego nawigują.
Pozostawia to NavigationCoordinator jako pojedynczy punkt iniekcji i jest to obiekt niewidoczny, do którego można uzyskać dostęp poza hierarchią widoków.
Podczas instalacji możesz zarejestrować odpowiednie klasy widoku, aby mógł zwrócić, używając pewnego rodzaju dopasowania z identyfikatorami przekazywanymi w czasie wykonywania. W niektórych przypadkach może działać coś tak prostego jak dopasowanie do identyfikatora docelowego. Lub dopasowanie do pary identyfikatorów hosta i miejsca docelowego.
W bardziej skomplikowanych przypadkach możesz napisać niestandardowy kontroler, który uwzględnia inne informacje specyficzne dla aplikacji.
Ponieważ jest wstrzykiwany przez środowisko, każdy widok może w dowolnym momencie zastąpić domyślnego Koordynatora Nawigacji i podać inny do swoich widoków podrzędnych.
źródło