From 53e8e0957e3266d3a1ed14c71843f3a6b9ef7696 Mon Sep 17 00:00:00 2001 From: Andrey Dorofeev Date: Wed, 12 Nov 2025 10:14:19 +0700 Subject: [PATCH 1/3] update ios specific onboarding and new swiftUI navigation --- learning/ios/navigation-old.md | 157 +++++++++++++++ learning/ios/navigation.md | 251 +++++++++++++----------- learning/kotlin-native/swift-interop.md | 19 +- onboarding/ios-specific.md | 8 +- 4 files changed, 315 insertions(+), 120 deletions(-) create mode 100644 learning/ios/navigation-old.md diff --git a/learning/ios/navigation-old.md b/learning/ios/navigation-old.md new file mode 100644 index 000000000..500dd7a34 --- /dev/null +++ b/learning/ios/navigation-old.md @@ -0,0 +1,157 @@ +# Навигация + +В основе навигации лежат координаторы. Каждый координатор покрывает логически связанный блок +функционала, который чаще всего состоит из нескольких экранов. При этом между собой они независимы и +отвечают только за цепочку переходов внутри себя. Также имеют возможность получать настройку +действия, которое должно быть выполнено после завершения блока ответственности координатора. + +## Пример + +Предположим, что у нас есть приложение с авторизацией и списком новостей, с которого +можно перейти к детальному просмотру каждой новости и в раздел настроек для конфигурации отображения новостей. + +Это разобьётся на 4 координатора: + +```mermaid +graph TD + AppCoordinator --> AuthCoordinator + AppCoordinator --> NewsCoordinator + NewsCoordinator --> SettingsCoordinator +``` + +- AppCoordinator + - Стартовый координатор. Всегда является первой входной точкой, определяет, куда должен выполниться дальнейший переход при запуске приложения + - Если юзер не авторизован - запустит координатор авторизации и в качестве completionHandler-а укажет ему переход на список новостей в случае успешной авторизации + - Если юзер уже авторизован - запустит координатор просмотра списка новостей +- AuthCoordinator + - Запустит процесс авторизации + - Будет совершать переходы по всем требуемым шагам - например ввод логина/пароля, смс-кода, установки никнейма и т.п. + - По итогу успешной авторизации вызовет переданный ему на вход completionHandler. +- NewsCoordinator + - Отвечает за показ списка новостей + - Реализовывает переход в детали конкретной новости внутри этого же координатора + - При переходе в настройки создаёт координатор настроек, в качестве completionHandler-а может передать ему логику обновления своего списка новостей. Если в настройках изменились параметры - обновляет список +- SettingsCoordinator + - Отвечает за работу с экраном настроек + - При завершении работы и применении настроек вызывает completion, чтобы новости обновились + +# BaseCoordinator + +Чтобы работать с координаторами было проще, используется базовый класс, от которого наследуются +остальные. В директории `Common/Coordinator` вы найдете файлы `CoordinatorProtocol.swift` и `BaseCoordinator.swift`. Первый несет в себе протокол, под который подписан `BaseCoordinator` и описывает обязательные методы и поля: + +```swift +protocol Coordinator: AnyObject { + var completionHandler: (()->())? { get } + func start() + func clear() +} +``` + +По сути он должен иметь ровно три вещи - completionHandler, который вызовется при завершении его логической зоны ответственности. Функцию start, при вызове которой он начинает запускать свой флоу таким образом, каким считает нужным, и функцию clear, которая чистит сам координатор и все дочерние. + +Ну а второй несет сам класс базового координатора, который реализует этот протокол: + +```swift +class BaseCoordinator: NSObject, Coordinator, UINavigationControllerDelegate { + var childCoordinators: [Coordinator] = [] + var completionHandler: (() -> ())? + + let window: UIWindow + let factory: SharedFactory + + var navigationController: UINavigationController? + + init(window: UIWindow, factory: SharedFactory) { ... } + + func addDependency(_ coordinator: Child, completion: (() -> Void)? = nil) -> Child where Child : BaseCoordinator { ... } + + func clear() { ... } + + //Cases + //1. Initial with window - create NV, etc.. + //2. Exists navcontroller, + + func start() { + // + } + + func beginInNewNavigation(_ controller: UIViewController) -> UINavigationController { ... } + + func beginInExistNavigation(_ controller: UIViewController) { ... } + + func currentViewController() -> UIViewController { ... } +} + +``` + +Для инициализации необходим window и factory. Также можно указать NavigationController с предыдущего +координатора, для сохранения общей навигации. + +:::note + +Координаторам нужен factory для доступа к фабрикам фичей из общей библиотеки. + +::: + +Добавление и удаление зависимостей нужны для корректной очистки связей и памяти при построении +цепочек координаторов. + +Также есть вспомогательные методы, которые позволяют получить текущий контроллер - +currentViewController и совершить переход назад - popBack. + +:::caution +От проекта к проекту базовый координатор может изменяться, обеспечивая дополнительные нужды проекта. +::: + +## AppСoordinator + +Теперь когда мы поняли принцип работы координаторов, посмотрим на класс `AppCoordinator`: + +```swift +class AppCoordinator: BaseCoordinator { + override func start() { + let vc = UIViewController() + vc.view.backgroundColor = .green + self.window.rootViewController = vc + } +} +``` + +В данном случае, главный координатор совсем простой - создает контроллер зелёного цвета и делает его главным экраном window. + +Теперь посмотрим где происходит создание главного координатора. Идём в `AppDelegate.swift`: + +```swift + // .... + + // переменная координатора + private (set) var coordinator: AppCoordinator! + + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { + + //... + + // его инициализация + coordinator = AppCoordinator.init( + window: self.window!, + factory: AppComponent.factory + ) + // запуск координатора + coordinator.start() + + // .... + } +``` + +Теперь дальнейшая логика переходов зависит от текущего контроллера и действий юзера на нём. + +После данного разбора у вас должно сформироваться представление о том, какие подходы мы используем +для реализации навигации в iOS-приложении. + +## Материалы + +- [Статья - How to use the coordinator pattern in iOS apps](https://www.hackingwithswift.com/articles/71/how-to-use-the-coordinator-pattern-in-ios-apps) +- [Статья - Coordinator Tutorial for iOS: Getting Started](https://www.raywenderlich.com/158-coordinator-tutorial-for-ios-getting-started) +- [Видео-разбор](https://www.youtube.com/watch?v=Pt9TGFzLVzc) использования `ApplicationCoordinator` для навигации между экранами diff --git a/learning/ios/navigation.md b/learning/ios/navigation.md index 500dd7a34..7e385d824 100644 --- a/learning/ios/navigation.md +++ b/learning/ios/navigation.md @@ -1,157 +1,178 @@ -# Навигация +# Навигация для SwiftUI -В основе навигации лежат координаторы. Каждый координатор покрывает логически связанный блок -функционала, который чаще всего состоит из нескольких экранов. При этом между собой они независимы и -отвечают только за цепочку переходов внутри себя. Также имеют возможность получать настройку -действия, которое должно быть выполнено после завершения блока ответственности координатора. +В стандартном `NavigationStack` есть ограничения: нельзя просто очистить стек экранов, неудобно обрабатывать logout, сложно управлять переходами между флоу. +Чтобы это решить, мы используем кастомный навигационный слой, построенный на базе: +- **AppRouter** — управляет навигационными событиями (`push`, `pop`, `replace`, `replaceStack`, `popUntil` и др.) через Combine. +- **AppRouterHost** — хост для `NavigationStack`, слушает команды роутера и обновляет стек экранов. +- **AppRoute** — перечисление маршрутов приложения (`signIn`, `main`, `detail`). -## Пример +Главное преимущество такого подхода — полный контроль над стеком навигации, анимациями и маршрутизацией. -Предположим, что у нас есть приложение с авторизацией и списком новостей, с которого -можно перейти к детальному просмотру каждой новости и в раздел настроек для конфигурации отображения новостей. +## AppRoute -Это разобьётся на 4 координатора: - -```mermaid -graph TD - AppCoordinator --> AuthCoordinator - AppCoordinator --> NewsCoordinator - NewsCoordinator --> SettingsCoordinator -``` - -- AppCoordinator - - Стартовый координатор. Всегда является первой входной точкой, определяет, куда должен выполниться дальнейший переход при запуске приложения - - Если юзер не авторизован - запустит координатор авторизации и в качестве completionHandler-а укажет ему переход на список новостей в случае успешной авторизации - - Если юзер уже авторизован - запустит координатор просмотра списка новостей -- AuthCoordinator - - Запустит процесс авторизации - - Будет совершать переходы по всем требуемым шагам - например ввод логина/пароля, смс-кода, установки никнейма и т.п. - - По итогу успешной авторизации вызовет переданный ему на вход completionHandler. -- NewsCoordinator - - Отвечает за показ списка новостей - - Реализовывает переход в детали конкретной новости внутри этого же координатора - - При переходе в настройки создаёт координатор настроек, в качестве completionHandler-а может передать ему логику обновления своего списка новостей. Если в настройках изменились параметры - обновляет список -- SettingsCoordinator - - Отвечает за работу с экраном настроек - - При завершении работы и применении настроек вызывает completion, чтобы новости обновились - -# BaseCoordinator - -Чтобы работать с координаторами было проще, используется базовый класс, от которого наследуются -остальные. В директории `Common/Coordinator` вы найдете файлы `CoordinatorProtocol.swift` и `BaseCoordinator.swift`. Первый несет в себе протокол, под который подписан `BaseCoordinator` и описывает обязательные методы и поля: +`AppRoute` - это основа навигации. Каждый экран, на который вы хотите перейти, должен быть кейсом этого `enum`. ```swift -protocol Coordinator: AnyObject { - var completionHandler: (()->())? { get } - func start() - func clear() +import SwiftUI + +enum AppRoute: Hashable { + case signIn + case main + case detail(id: Int) } + ``` +- Enum обязательно должен реализовывать `Hashable`, чтобы `NavigationStack` мог работать с этим enum'ом. +- Параметры экранов передаются через associated values `(.detail(id: Int))`. +- В сложных проектах можно заводить несколько Route, например AuthRoute для авторизации и AppRoute для основного функционала приложения. -По сути он должен иметь ровно три вещи - completionHandler, который вызовется при завершении его логической зоны ответственности. Функцию start, при вызове которой он начинает запускать свой флоу таким образом, каким считает нужным, и функцию clear, которая чистит сам координатор и все дочерние. +## AppRouter -Ну а второй несет сам класс базового координатора, который реализует этот протокол: +AppRouter — это "пульт управления" навигацией. Он сам не переключает экраны, а только отправляет команды. ```swift -class BaseCoordinator: NSObject, Coordinator, UINavigationControllerDelegate { - var childCoordinators: [Coordinator] = [] - var completionHandler: (() -> ())? - - let window: UIWindow - let factory: SharedFactory - - var navigationController: UINavigationController? - - init(window: UIWindow, factory: SharedFactory) { ... } - - func addDependency(_ coordinator: Child, completion: (() -> Void)? = nil) -> Child where Child : BaseCoordinator { ... } - - func clear() { ... } - - //Cases - //1. Initial with window - create NV, etc.. - //2. Exists navcontroller, - - func start() { - // +class AppRouter: ObservableObject { + // Приватные сабджекты для управления событиями + private let commandSubject = PassthroughSubject, Never>() + + // Публичные паблишеры + var commandPublisher: AnyPublisher, Never> { + commandSubject.eraseToAnyPublisher() } - - func beginInNewNavigation(_ controller: UIViewController) -> UINavigationController { ... } - - func beginInExistNavigation(_ controller: UIViewController) { ... } - - func currentViewController() -> UIViewController { ... } -} -``` + /// Добавить экран следующим в стеке навигации + func push(_ route: T) { + commandSubject.send(.push(route: route)) + } -Для инициализации необходим window и factory. Также можно указать NavigationController с предыдущего -координатора, для сохранения общей навигации. + /// Заменить весь стек навигации на новый роут + func replace(_ route: T) { + commandSubject.send(.replace(route: route)) + } + + /// Заменить весь стек навигации на другой стек + func replaceStack(_ stack: [T]) { + commandSubject.send(.replaceStack(stack: stack)) + } + + func popUntil(popIf: @escaping (T) -> Bool) { + commandSubject.send(.popUntil(popIf: popIf)) + } + + /// Убрать из стека навигации роуты удовлетворяющие условию и добавить новый + func popUntilAndPush(popIf: @escaping (T) -> Bool, pushRoute: T) { + commandSubject.send(.popUntilAndPush(popIf: popIf, pushRoutes: [pushRoute])) + } -:::note + /// Убрать из стека навигации роуты удовлетворяющие условию и добавить несколько новых + func popUntilAndPush(popIf: @escaping (T) -> Bool, pushRoutes: [T]) { + commandSubject.send(.popUntilAndPush(popIf: popIf, pushRoutes: pushRoutes)) + } -Координаторам нужен factory для доступа к фабрикам фичей из общей библиотеки. + /// Убрать из стека навигации один экран + func pop() { + commandSubject.send(.pop) + } +} -::: +enum RouterCommand { + case push(route: T) + case popUntil(popIf: (T) -> Bool) + case popUntilAndPush(popIf: (T) -> Bool, pushRoutes: [T]) + case pop + case replace(route: T) + case replaceStack(stack: [T]) +} -Добавление и удаление зависимостей нужны для корректной очистки связей и памяти при построении -цепочек координаторов. +``` -Также есть вспомогательные методы, которые позволяют получить текущий контроллер - -currentViewController и совершить переход назад - popBack. +Пример использования: -:::caution -От проекта к проекту базовый координатор может изменяться, обеспечивая дополнительные нужды проекта. -::: +```swift +@EnvironmentObject var router: AppRouter -## AppСoordinator +router.push(.main) // перейти на главный экран +router.pop() // вернуться назад +router.replace(.signIn) // очистить стек и перейти на авторизацию -Теперь когда мы поняли принцип работы координаторов, посмотрим на класс `AppCoordinator`: +``` +Роутер удобно получать через @EnvironmentObject, так его не нужно передавать вручную во все экраны. ```swift -class AppCoordinator: BaseCoordinator { - override func start() { - let vc = UIViewController() - vc.view.backgroundColor = .green - self.window.rootViewController = vc +struct AuthScreen: View { + @EnvironmentObject var router: AppRouter + + var body: some View { + Button("Войти") { + router.replace(.main) + } } } + ``` -В данном случае, главный координатор совсем простой - создает контроллер зелёного цвета и делает его главным экраном window. +## AppRouterHost -Теперь посмотрим где происходит создание главного координатора. Идём в `AppDelegate.swift`: +`AppRouterHost` связывает `AppRouter` с `NavigationStack`. Он слушает команды роутера и обновляет, какие экраны должны быть показаны. -```swift - // .... +Основные свойства: +- `rootRoute` — корневой экран (с которого начинается навигация). +- `navigationPath` — стек экранов, которые уже открыты. +- `routeView` - билдер конкретного экрана. В аргумент приходит роут. Когда используем enum для роута - делаем просто switch по вариантам этого enum и возвращаем нужные экраны. - // переменная координатора - private (set) var coordinator: AppCoordinator! +Пример: - func application(_ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { +```swift +AppRouterHost(initialRoute: .signIn) { router, route in + switch route { + case .signIn: + AuthScreen() + case .main: + MainScreen() + case let .detail(id): + DetailScreen(id: id) + } +} +``` +- При старте открывается signIn. +- Если вызвать router.push(.main) → перейдем на экран main. +- Если вызвать router.pop() → вернемся обратно на signIn. + +В итоге у нас получается такая последовательность: - //... +Экран → AppRouter (отправил команду) → AppRouterHost (выполнил) → NavigationStack (обновился) - // его инициализация - coordinator = AppCoordinator.init( - window: self.window!, - factory: AppComponent.factory - ) - // запуск координатора - coordinator.start() - // .... +## RootScreenView + +Как видно из названия, это корневой экран приложения, именно он решает какой экран показать в данный момент. Если в приложении несколько роутов, то именно здесь будет происходить переключение между ними + +```swift +struct RootScreenView: View { + @State private var root: RootScreen = .splash + + var body: some View { + LogoutNavigationHookView(onLogout: { + root = .mainFlow(route: .signIn) + }) { + switch root { + case .splash: + SplashScreen(root: $root) + case let .mainFlow(route): + MainNavigationView(initialRoute: route) + } + } } +} ``` -Теперь дальнейшая логика переходов зависит от текущего контроллера и действий юзера на нём. +При запуске приложения `root = .splash`, соответсвенно показываться будет SplashScreen. +После этого сплешскрин определяет, авторизован юзер или нет. +Если не авторизван то меняет значение `root` на `.mainFlow(.signIn)`. +Если авторизован, то меняет значение `root` на `.mainFlow(.main)`. +В этот момент RootScreenView переключает показ на `MainNavigationView`. +Если пользователь нажимает "Выйти", то срабатывает `LogoutNavigationHookView`, и всё сбрасывается на SignIn. -После данного разбора у вас должно сформироваться представление о том, какие подходы мы используем -для реализации навигации в iOS-приложении. -## Материалы -- [Статья - How to use the coordinator pattern in iOS apps](https://www.hackingwithswift.com/articles/71/how-to-use-the-coordinator-pattern-in-ios-apps) -- [Статья - Coordinator Tutorial for iOS: Getting Started](https://www.raywenderlich.com/158-coordinator-tutorial-for-ios-getting-started) -- [Видео-разбор](https://www.youtube.com/watch?v=Pt9TGFzLVzc) использования `ApplicationCoordinator` для навигации между экранами +- [Навигяция для UIKit через координаторы (Архив)](./navigation-old.md) diff --git a/learning/kotlin-native/swift-interop.md b/learning/kotlin-native/swift-interop.md index 98a7c1565..7267b9e38 100644 --- a/learning/kotlin-native/swift-interop.md +++ b/learning/kotlin-native/swift-interop.md @@ -4,7 +4,24 @@ sidebar_position: 3 # Kotlin/Swift interop -Полезные ссылки: +## Top-level функции в Kotlin Multiplatform и их вызов из Swift + +В Kotlin мы можем объявлять глобальные функции (top-level functions), то есть функции вне классов и объектов: +```kotlin +fun log(message: String) { + println(message) +} +``` + +На Android такие функции вызываются напрямую. Но при интеграции с iOS возникает ощущение, что это функция не видна в Swift коде. +Дело в том, что Swift получает доступ к Kotlin-функциям через Objective-C. В Objective-C глобальных функций нет, поэтому компилятор Kotlin при экспорте в iOS “упаковывает” top-level функции в специальные классы. +Имя этого класса формируется из названия файла, где функция была определена. Если, например, функция log находится в Logger.kt, то в Swift её нужно вызвать так: +```swift +LoggerKt.log("Hello from iOS") +``` +То есть доступ к функции осуществляется не напрямую, а через сгенерированный класс LoggerKt. + +## Полезные ссылки: - [Interoperability with Swift/Objective-C](https://kotlinlang.org/docs/native-objc-interop.html) - [Russell Wolf - The Kotlin/Swift boundary](https://vimeo.com/625847664) diff --git a/onboarding/ios-specific.md b/onboarding/ios-specific.md index 8d4125892..4eae83921 100644 --- a/onboarding/ios-specific.md +++ b/onboarding/ios-specific.md @@ -6,11 +6,11 @@ sidebar_position: 7 Важные особенности для iOS разработчиков: -- [Проблемы с Release сборками под iOS](../learning/problem-solving/kotlin-native-release-build-failed) -- [Влияние Kotlin/Native на размер бинарника](../learning/kotlin-native/size_impact) -- [Как читать Stacktrace Kotlin/Native на iOS](../learning/kotlin-native/stacktraces) -- [Разница Extension'ов в Kotlin и Swift](../learning/kotlin-native/swift-extensions) - [Как Kotlin будет виден со стороны Swift](../learning/kotlin-native/swift-interop) - [Как Kotlin попадает в Xcode через Cocoapods](../learning/ios/pods) - [Как мы работаем на проектах с конфигурациями](../learning/ios/configuration) - [Как мы делаем навигацию](../learning/ios/navigation) +- [Разница Extension'ов в Kotlin и Swift](../learning/kotlin-native/swift-extensions) +- [Влияние Kotlin/Native на размер бинарника](../learning/kotlin-native/size_impact) +- [Как читать Stacktrace Kotlin/Native на iOS](../learning/kotlin-native/stacktraces) + From 18b46b08a3403654cc39e3011dafcadbd0d55382 Mon Sep 17 00:00:00 2001 From: Andrey Dorofeev Date: Fri, 21 Nov 2025 17:37:20 +0700 Subject: [PATCH 2/3] Update iOS navigation guides and Kotlin/Swift interop notes --- .../{navigation.md => navigation-swiftui.md} | 89 ++++++++++++++++++- ...{navigation-old.md => navigation-uikit.md} | 2 +- learning/kotlin-native/swift-interop.md | 9 ++ 3 files changed, 96 insertions(+), 4 deletions(-) rename learning/ios/{navigation.md => navigation-swiftui.md} (67%) rename learning/ios/{navigation-old.md => navigation-uikit.md} (99%) diff --git a/learning/ios/navigation.md b/learning/ios/navigation-swiftui.md similarity index 67% rename from learning/ios/navigation.md rename to learning/ios/navigation-swiftui.md index 7e385d824..bc1270384 100644 --- a/learning/ios/navigation.md +++ b/learning/ios/navigation-swiftui.md @@ -96,8 +96,30 @@ router.pop() // вернуться назад router.replace(.signIn) // очистить стек и перейти на авторизацию ``` -Роутер удобно получать через @EnvironmentObject, так его не нужно передавать вручную во все экраны. +Как router передается между экранами: +Роутер удобно получать через `@EnvironmentObject`, так его не нужно вручную передавать во все экраны. +- AppRouterHost создаёт и хранит объект `AppRouter`. +- Все экраны внутри этого хоста получают роутер через `environmentObject(router)`. +- SwiftUI автоматически вкладывает объект в иерархию view, так что любой экран, который находится внутри `AppRouterHost`, может использовать его через @`EnvironmentObject`. +В AppRouterHost: + +```swift +@ObservedObject private var router = AppRouter() + +var body: some View { + NavigationStack(path: $navigationPath) { + routeView(router, rootRoute) + .navigationDestination( + or: T.self, + destination: { routeView(router, $0) } + ) + }.environmentObject(router) // роутер передается всем дочерним экранам +} + +``` + +В любом дочернем экране: ```swift struct AuthScreen: View { @EnvironmentObject var router: AppRouter @@ -108,7 +130,6 @@ struct AuthScreen: View { } } } - ``` ## AppRouterHost @@ -173,6 +194,68 @@ struct RootScreenView: View { В этот момент RootScreenView переключает показ на `MainNavigationView`. Если пользователь нажимает "Выйти", то срабатывает `LogoutNavigationHookView`, и всё сбрасывается на SignIn. +## LogoutNavigationHookView + +В приложениях часто нужно сбросить навигацию при выходе пользователя. Для этого используется LogoutNavigationHookView. +Он оборачивает контент и отслеживает события логаута через logoutHandler. Когда происходит событие логаута, вызывается onLogout(), и можно, например, сбросить стек навигации на экран авторизации. + +```swift +import Combine +import MultiPlatformLibrary +import SwiftUI + +struct LogoutNavigationHookView: View { + + // LogoutNavigationHookView слушает события логаута через logoutHandler. + private var logoutHandler: LogoutHandler = Koin.instance.getLogoutHandler() + + let onLogout: () -> Void + let content: () -> Content + + init(onLogout: @escaping () -> Void, @ViewBuilder content: @escaping () -> Content) { + self.onLogout = onLogout + self.content = content + } + + var body: some View { + content() + .onReceive( + logoutHandler.logoutEvents.toPublisher() + .catch { _ in Empty() } + .assertNoFailure() + ) { _ in + + // При логауте вызывается onLogout(), которое сбрасывает root. + onLogout() + } + } +} + +``` + +В этом примере вызов `onLogout` приводит к сбросу значения root на .mainFlow(.signIn). +`RootScreenView` реагирует на изменение root и показывает экран авторизации. + +```swift +struct RootScreenView: View { + @State private var root: RootScreen = .splash + var body: some View { + LogoutNavigationHookView(onLogout: { + + // Сбрасываем стек навигации на экран авторизации + root = .mainFlow(route: .signIn) + }) { + switch root { + case .splash: + SplashScreen(root: $root) + case let .mainFlow(route): + MainNavigationView(initialRoute: route) + } + } + } +} +``` +Если мы получаем событие logout на любом экране, весь стек навигации автоматически сбрасывается. -- [Навигяция для UIKit через координаторы (Архив)](./navigation-old.md) +- [Навигация для UIKit через координаторы (Архив)](./navigation-uikit.md) diff --git a/learning/ios/navigation-old.md b/learning/ios/navigation-uikit.md similarity index 99% rename from learning/ios/navigation-old.md rename to learning/ios/navigation-uikit.md index 500dd7a34..f7b9ab9bc 100644 --- a/learning/ios/navigation-old.md +++ b/learning/ios/navigation-uikit.md @@ -1,4 +1,4 @@ -# Навигация +# Навигация UIKit В основе навигации лежат координаторы. Каждый координатор покрывает логически связанный блок функционала, который чаще всего состоит из нескольких экранов. При этом между собой они независимы и diff --git a/learning/kotlin-native/swift-interop.md b/learning/kotlin-native/swift-interop.md index 7267b9e38..1fde35644 100644 --- a/learning/kotlin-native/swift-interop.md +++ b/learning/kotlin-native/swift-interop.md @@ -21,6 +21,15 @@ LoggerKt.log("Hello from iOS") ``` То есть доступ к функции осуществляется не напрямую, а через сгенерированный класс LoggerKt. +Однако при использовании [SKIE](https://skie.touchlab.co/intro) всё работает так, как ожидается - глобальные функции становятся настоящими глобальными функциями в Swift. + +И тогда на swift мы сможем писать так: +```swift +log("Hello from iOS") +``` + +Подробнее: https://skie.touchlab.co/features/global-functions + ## Полезные ссылки: - [Interoperability with Swift/Objective-C](https://kotlinlang.org/docs/native-objc-interop.html) From ef0779a5e2e5b81fd43bbab162cdfac2ebda2a08 Mon Sep 17 00:00:00 2001 From: Andrey Dorofeev Date: Fri, 21 Nov 2025 17:47:54 +0700 Subject: [PATCH 3/3] fix navigation docs references to navigation-swiftui --- onboarding/ios-specific.md | 2 +- onboarding/project-inside.md | 2 +- university/4-icerock-basics/navigation.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/onboarding/ios-specific.md b/onboarding/ios-specific.md index 4eae83921..affb4dc37 100644 --- a/onboarding/ios-specific.md +++ b/onboarding/ios-specific.md @@ -9,7 +9,7 @@ sidebar_position: 7 - [Как Kotlin будет виден со стороны Swift](../learning/kotlin-native/swift-interop) - [Как Kotlin попадает в Xcode через Cocoapods](../learning/ios/pods) - [Как мы работаем на проектах с конфигурациями](../learning/ios/configuration) -- [Как мы делаем навигацию](../learning/ios/navigation) +- [Как мы делаем навигацию](../learning/ios/navigation-swiftui) - [Разница Extension'ов в Kotlin и Swift](../learning/kotlin-native/swift-extensions) - [Влияние Kotlin/Native на размер бинарника](../learning/kotlin-native/size_impact) - [Как читать Stacktrace Kotlin/Native на iOS](../learning/kotlin-native/stacktraces) diff --git a/onboarding/project-inside.md b/onboarding/project-inside.md index a9e044ee0..b97f389c1 100644 --- a/onboarding/project-inside.md +++ b/onboarding/project-inside.md @@ -915,7 +915,7 @@ class AppComponent { Мы поняли что является отправной точкой нашего приложения, а теперь нам нужно понять как построена навигация в iOS приложение и какие подходы при работе с ней мы используем. -Для этого можете ознакомиться со [статьей в разделе обучения](../learning/ios/navigation). +Для этого можете ознакомиться со [статьей в разделе обучения](../learning/ios/navigation-swiftui). ## master.sh diff --git a/university/4-icerock-basics/navigation.md b/university/4-icerock-basics/navigation.md index 26ce8d9a1..560411913 100644 --- a/university/4-icerock-basics/navigation.md +++ b/university/4-icerock-basics/navigation.md @@ -11,7 +11,7 @@ sidebar_position: 8 ## iOS -Для понимания того, как будет реализована навигация в `iOS` приложениях на проектах, ознакомьтесь сначала с [видео-разбором](https://www.youtube.com/watch?v=Pt9TGFzLVzc) использования `ApplicationCoordinator` для навигации между экранами, а затем со [статьей](../../learning/ios/navigation) и материалами из нее. +Для понимания того, как будет реализована навигация в `iOS` приложениях на проектах, ознакомьтесь сначала с [видео-разбором](https://www.youtube.com/watch?v=Pt9TGFzLVzc) использования `ApplicationCoordinator` для навигации между экранами, а затем со [статьей](../../learning/ios/navigation-swiftui) и материалами из нее. В наших проектах, для верстки и навигации на iOS мы больше не будем использовать `.storyboard`, вместо этого мы будем пользоваться следующими инструментами: - `AppCoordinator` - главный координатор приложения, который будет запускать другие координаторы в зависимости от входных данных