Swiftui의 코디네이터 패턴의 단순하고 강력하며 우아한 구현. Stinsen은 100% Swiftui를 사용하여 작성하여 iOS, TVOS, WatchOS 및 MacOS 장치에서 원활하게 작동합니다.
우리는 모두 Uikit의 라우팅이 더 큰 크기의 응용 프로그램으로 작업 할 때 또는 MVVM과 같은 건축 패턴을 적용하려고 할 때 우아하게 수행하기가 어려울 수 있다는 것을 알고 있습니다. 불행히도, Swiftui Out Of The Box는 Uikit과 같은 많은 문제를 겪고 있습니다. NavigationLink 와 같은 개념은 View 계층에서 라이브로, 우리는 여전히 흐름과 경로에 대한 명확한 개념이 없습니다. Stinsen은 이러한 통증을 완화시키기 위해 만들어졌으며 코디네이터 패턴 의 구현입니다. Swiftui로 작성된이 제품은 완전히 교차 플랫폼이며 @EnvironmentObject 와 같은 기본 도구를 사용합니다. 목표는 Swiftui에서 Stinsen이 누락 된 도구처럼 느끼게하여 코딩 스타일과 일반적인 원칙을 준수하는 것입니다.
일반적으로 Swiftui에서는 NavigationLink 사용하여 내비게이션 스택에 다른 뷰를 추가하는 것을 처리해야합니다. 우리가 여기에있는 것은보기 사이의 단단한 커플 링입니다. 뷰는 그 사이를 탐색 할 수있는 다른 모든 견해를 미리 알아야하기 때문입니다. 또한, 견해는 단일 책임 원칙 (SRP)을 위반하는 것입니다. 2015 년 NSSpain 회의에서 Soroush Khanlou가 iOS 커뮤니티에 제시 한 코디네이터 패턴을 사용 하여이 책임을 고등학교 인 코디네이터에게 위임 할 수 있습니다.
내비게이션 스택 사용 예 :
final class UnauthenticatedCoordinator : NavigationCoordinatable {
let stack = NavigationStack ( initial : UnauthenticatedCoordinator . start )
@ Root var start = makeStart
@ Route ( . modal ) var forgotPassword = makeForgotPassword
@ Route ( . push ) var registration = makeRegistration
func makeRegistration ( ) -> RegistrationCoordinator {
return RegistrationCoordinator ( )
}
@ ViewBuilder func makeForgotPassword ( ) -> some View {
ForgotPasswordScreen ( )
}
@ ViewBuilder func makeStart ( ) -> some View {
LoginScreen ( )
}
} @Route S는 현재 코디네이터로부터 수행 할 수있는 가능한 모든 경로와 수행 될 전환을 정의합니다. 오른쪽의 값은 라우팅시 실행되는 공장 기능입니다. 함수는 Swiftui보기 또는 다른 코디네이터를 반환 할 수 있습니다. @Root 전환이없는 다른 유형의 경로와 코디네이터의 NavigationStack 스택의 첫 번째보기를 정의하는 데 사용되는 다른 유형의 경로.
STINSEN OUT BOX에서 코디네이터가 구현할 수있는 두 가지 종류의 Coordinatable 프로토콜이 있습니다.
NavigationCoordinatable 내비게이션 흐름의 경우. 내비게이션 스택을 누르려면 NavigationViewCoordinator에이를 감싸십시오.TabCoordinatable tabviews의 경우. 또한 Stinsen 에는 2 개의 코디네이터, ViewWrapperCoordinator 및 NavigationViewCoordinator 도 있습니다. ViewWrapperCoordinator 는 코디네이터로서 서브 클래스 또는 바로 코디네이터를 view로 랩핑 할 수 있으며 NavigationViewCoordinator 코디네이터를 NavigationView 에 랩핑하는 ViewWrapperCoordinator 서브 클래스입니다.
코디네이터의보기는 .view() 사용하여 만들 수 있으므로 사용자에게 코디네이터를 표시하려면 다음과 같은 작업을 수행합니다.
struct StinsenApp : App {
var body : some Scene {
WindowGroup {
MainCoordinator ( ) . view ( )
}
}
} Stinsen은 전체 앱 또는 앱의 일부만 전원을 공급하는 데 사용할 수 있습니다. 당신은 여전히 일반적인 swiftui NavigationLink s와 stinsen이 관리하는 뷰 내부의 모달 시트를 사용할 수 있습니다.
코디네이터와 뷰를 모두 참조하는 라우터를 사용하여보기에서 전환을 수행 할 수 있습니다. 보기 내부에서 @EnvironmentObject 사용하여 라우터를 가져올 수 있습니다. 라우터를 사용하면 다른 경로로 전환 할 수 있습니다.
struct TodosScreen : View {
@ EnvironmentObject var todosRouter : TodosCoordinator . Router
var body : some View {
List {
/* ... */
}
. navigationBarItems (
trailing : Button (
action : {
// Transition to the screen to create a todo:
todosRouter . route ( to : . createTodo )
} ,
label : {
Image ( systemName : " doc.badge.plus " )
}
)
)
}
} 트리 앞부에 나타난 라우터 참조 코디네이터도 가져올 수도 있습니다. 예를 들어 TabView 내부의보기에서 탭을 전환 할 수 있습니다.
코디네이터 자체에서 라우팅을 직접 수행 할 수 있습니다. 코디네이터가 논리를 갖기를 원하거나 코디네이터를 전달하는 경우 다음과 같습니다.
final class MainCoordinator : NavigationCoordinatable {
@ Root var unauthenticated = makeUnauthenticated
@ Root var authenticated = makeAuthenticated
/* ... */
init ( ) {
/* ... */
cancellable = AuthenticationService . shared . status . sink { [ weak self ] status in
switch status {
case . authenticated ( let user ) :
self ? . root ( . authentiated , user )
case . unauthenticated :
self ? . root ( . unauthentiated )
}
}
}
} 라우터/코디네이터에서 수행 할 수있는 작업은 사용 된 코디네이터의 종류에 따라 다릅니다. 예를 들어, NavigationCoordinatable 사용하면 수행 할 수있는 기능 중 일부는 다음과 같습니다.
popLast - 스택에서 마지막 항목을 제거합니다. Stinsen은 뷰가 모드로 표시되거나 푸시되었는지 여부는 신경 쓰지 않으며 동일한 기능이 두 가지 모두에 사용됩니다.pop - 스택에서 뷰를 제거합니다. 이 기능은 라우터에서만 수행 할 수 있습니다. 라우터 만 팝하려는 뷰에 대해 알고 있기 때문입니다.popToRoot 스택을 지 웁니다.root - 루트를 변경합니다 (즉, 스택의 첫 번째보기). 뿌리가 이미 활성 루트 인 경우 아무것도하지 않습니다.route - 다른 경로로 탐색합니다.focusFirst 첫 번째 항목에서 시작하여 스택에 존재하는 경우 지정된 경로를 찾습니다. 발견되면 그 후 모든 것을 제거합니다.dismissCoordinator 전체 코디네이터를 삭제하고 나무에서 관련된 어린이입니다.
REPO를 복제하고 STINSENAPP를 예제/앱 에서 실행하여 Stinsen을 사용하는 방법에 대한 느낌을 얻으십시오. Stinsenapp는 iOS, TVOS, WatchOS 및 MacOS에서 작동합니다. Stinsen이 사용할 수있는 많은 기능을 전시하려고 시도합니다. 이 readme의 대부분의 코드는 샘플 앱에서 나옵니다. Swiftui 에서 Stinsen을 사용하여 테스트 가능한 MVM-C 아키텍처를 적용하는 방법을 보여주는 예가 있습니다.
@EnvironmentObject View 내에서만 액세스 할 수 있으므로 Stinsen은 뷰 모델에서 몇 가지 라우팅 방법을 제공합니다. 코디네이터를 ìnitializer를 통해 주입하거나 생성시 등록하여 종속성 주입 프레임 워크를 통해 뷰 모델에서 해결할 수 있습니다. 최대의 제어 및 기능을 갖기 때문에 권장되는 방법입니다.
다른 방법은 onAppear 함수를 사용하여 라우터를 전달하는 것입니다.
struct TodosScreen : View {
@ StateObject var viewModel = TodosViewModel ( )
@ EnvironmentObject var projects : TodosCoordinator . Router
var body : some View {
List {
/* ... */
}
. onAppear {
viewModel . router = projects
}
}
} RouterStore 라고 불리는 것을 사용하여 라우터를 리테로이션 할 수도 있습니다. RouterStore 라우터의 인스턴스를 저장하고 사용자 정의 PropertyWrapper를 통해 얻을 수 있습니다.
라우터를 검색하려면 :
class LoginScreenViewModel : ObservableObject {
// directly via the RouterStore
var main : MainCoordinator . Router ? = RouterStore . shared . retrieve ( )
// via the RouterObject property wrapper
@ RouterObject
var unauthenticated : Unauthenticated . Router ?
init ( ) {
}
func loginButtonPressed ( ) {
main ? . root ( . authenticated )
}
func forgotPasswordButtonPressed ( ) {
unauthenticated ? . route ( to : . forgotPassword )
}
}이 예제를 실제로 보려면 예제/MVVM 에서 MVVM-App을 확인하십시오.
때로는 코디네이터가 생성 한보기를 사용자 정의하고 싶을 때가 있습니다. NavigationCoordInatable 및 TabcoordInatable은 그렇게하기 위해 구현할 수있는 customize 기능이 있습니다.
final class AuthenticatedCoordinator : TabCoordinatable {
/* ... */
@ ViewBuilder func customize ( _ view : AnyView ) -> some View {
view
. onReceive ( Services . shared . $authentication ) { authentication in
switch authentication {
case . authenticated :
self . root ( . authenticated )
case . unauthenticated :
self . root ( . unauthenticated )
}
}
}
}
} 또한 사용자 정의하는 데 사용할 수있는 ViewWrapperCoordinator 도 있습니다.
코디네이터/라우터의 대부분의 기능은 코디네이터를 반환하기 때문에 결과를 사용하여 함께 연결하여 필요한 경우보다 고급 라우팅을 수행 할 수 있습니다. 예를 들어, 로그인 후 탭을 변경하고 앱의 어느 곳에서나 특정 할 일정을 선택하는 Swiftui 버튼을 만들려면 다음과 같습니다.
VStack {
ForEach ( todosStore . favorites ) { todo in
Button ( todo . name ) {
authenticatedRouter
. focusFirst ( . todos )
. child
. popToRoot ( )
. route ( to : . todo , todo . id )
}
}
} authenticatedRouter 참조하는 AuthenticatedCoordinator 는 TabCoordinatable 이므로 기능은 다음과 같습니다.
focusFirst : 경로 todos 로 표시되는 첫 번째 탭을 반환하고 이미 활성 탭이 아닌 한 활성 탭으로 만드십시오.child : 아이를 돌려주고, Todos -tab은 NavigationViewCoordinator 이고 아이는 NavigationCoordinatable 입니다.popToRoot : 참석했을 수도 있고 없을 수도있는 어린이들을 떠날 것입니다.route : 지정된 ID를 사용하여 경로 Todo 로 경로로 이동합니다. Stinsen은 Keypaths를 사용하여 경로를 나타 내기 때문에 함수는 유형-안전하고 잘못된 체인을 만들 수 없습니다. 즉, A 에서 B 까지의 경로가 있고 B 에서 C에서 C에서 C 까지의 경로가있는 경우, 먼저 B 로 라우팅하지 않고 A 에서 C 로의 라우팅을 시도하면 앱이 컴파일되지 않습니다. 또한 TabCoordinatable 등에서 popToRoot() 와 같은 작업을 수행 할 수 없습니다.
반환 된 값을 사용하면 앱 내에서 쉽게 deeplink 할 수 있습니다.
final class MainCoordinator : NavigationCoordinatable {
@ ViewBuilder func customize ( _ view : AnyView ) -> some View {
view . onOpenURL { url in
if let coordinator = self . hasRoot ( . authenticated ) {
do {
// Create a DeepLink-enum
let deepLink = try DeepLink ( url : url , todosStore : coordinator . todosStore )
switch deepLink {
case . todo ( let id ) :
coordinator
. focusFirst ( . todos )
. child
. route ( to : . todo , id )
}
} catch {
print ( error . localizedDescription )
}
}
}
}
} Stinsen 에는 표준 Swiftui Views를위한 몇 가지 좌표가 제공됩니다. 예를 들어 햄버거 메뉴에 사용하려면 직접 만들어야합니다. 소스 코드를 확인하여 영감을 얻으십시오.
Stinsen은 두 가지 설치 방법 인 Cocoapods와 SPM을 지원합니다.
Xcode 및 프로젝트를 열고 File / Swift Packages / Add package dependency... Textfield " Package Repository URL 입력 "에서 https://github.com/rundfunk47/stinsen 작성하고 다음 두 번 누르십시오.
앱의 루트 디렉토리에서 Podfile 만듭니다. 추가하다
# Podfile
use_frameworks!
target 'YOUR_TARGET_NAME' do
pod 'Stinsen'
end
DoubleColumnNavigationViewStyle 지원하지 않습니다. 그 이유는 Swiftui의 isActive 문제로 인해 예상대로 작동하지 않기 때문입니다. 해결 방법 : uiviewRepresentable을 사용하거나 자신의 구현을 만듭니다.Byva에서 우리는 100% Swiftui 응용 프로그램을 만들기 위해 노력하고 있기 때문에 우리는이 요구와 다른 요구를 충족시키는 코디네이터 프레임 워크를 만들어야하는 것이 당연합니다. 이 프레임 워크는 생산에 사용되며 ~ 50 개의 흐름과 ~ 100 개의 화면을 관리합니다. 프레임 워크는 @rundfunk47에 의해 유지됩니다.
Stins는 "Station Master"의 경우 스웨덴어에서 짧고 Stinsen은 명확한 기사 인 "Station Master"입니다. 구어체 적 으로이 용어는 대부분 열차를 라우팅하는 기차 파견자를 지칭하는 데 사용되었습니다. 로고는 스웨덴 Linköping의 기차역 근처에 위치한 나무 의 나무 조각상을 기반으로합니다.
Stinsen v2의 가장 큰 변화는 Stinsen V1보다 유형-안전하다는 것입니다.
AnyCoordinatable 프로토콜로 대체되었습니다. 그것은 이전의 AnyCoordinatable 과 동일한 의무를 수행하지 않으며 버전 2의 유형 안전 라우팅에 맞지 않으므로 프로젝트에서 제거하십시오.route(to: .a) 우리는 route(to: .a) 사용합니다..view() 사용하십시오.@Root S를 사용하여 .root() 사용하여 동일한 기능을 얻으려면 스위치를 전환하십시오.Stinsen은 MIT 라이센스에 따라 릴리스됩니다. 자세한 내용은 라이센스를 참조하십시오.