Розробка навігації зі стеком екранів (Navigation Stack) для мобільного додатку
Навігація — те, що користувач відчуває кожну секунду роботи з додатком. Кривий стек екранів: анімація дергається, кнопка «Назад» веде не туди, deep link відкриває порожній екран — і рейтинг падає. Правильна навігація непомітна. Неправильна — дратує.
iOS: UINavigationController та SwiftUI NavigationStack
В UIKit навігація будується на UINavigationController. Типова помилка: push з будь-якого місця через UIApplication.shared.windows.first?.rootViewController. Це ломається на iPad, у modal-контекстах і при роботі з кількома сценами. Правильно: Coordinator-паттерн, де кожен coordinator володіє своїм UINavigationController і знає про свій участок графа навігації.
У SwiftUI з iOS 16+ з'явився NavigationStack з NavigationPath:
@State private var path = NavigationPath()
NavigationStack(path: $path) {
HomeView()
.navigationDestination(for: Route.self) { route in
switch route {
case .profile(let id): ProfileView(userId: id)
case .settings: SettingsView()
}
}
}
NavigationPath — типобезпечний стек, який можна зберегти, відновити та передати через deep link. До iOS 16 — NavigationView, який має баги з подвійними push на iPad.
Deep Links на iOS
Universal Links потребують apple-app-site-association на сервері та налаштування Associated Domains у Xcode. URL Schemes (myapp://profile/123) простіші, але перехоплюються будь-яким додатком. У SceneDelegate.scene(_:openURLContexts:) парсимо URL → конвертуємо в Route → пушимо в потрібний coordinator.
Android: Jetpack Navigation Component
Fragment backstack вручну — джерело багів: подвійні транзакції, неправильне збереження стану при поверненні. Jetpack Navigation Component (androidx.navigation) замінює ручне управління на декларативний Navigation Graph:
<navigation>
<fragment android:id="@+id/homeFragment" ...>
<action android:id="@+id/action_home_to_profile"
app:destination="@id/profileFragment"/>
</fragment>
<fragment android:id="@+id/profileFragment" ...>
<argument android:name="userId" app:argType="string"/>
</fragment>
</navigation>
Безпечна передача аргументів через Safe Args: генеровані HomeFragmentDirections.actionHomeToProfile(userId) замість Bundle.putString. Невідповідність типів — помилка компіляції, не runtime-краш.
З Compose — NavHost:
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen(onProfileClick = { id ->
navController.navigate("profile/$id")
}) }
composable("profile/{userId}") { backStack ->
val userId = backStack.arguments?.getString("userId")!!
ProfileScreen(userId = userId)
}
}
Deep links через <deepLink app:uri="myapp://profile/{userId}"/> у Navigation Graph або через NavDeepLinkBuilder.
React Native: React Navigation
React Navigation — стандарт для React Native. Stack Navigator, Tab Navigator, Drawer Navigator комбінуються:
const Stack = createNativeStackNavigator<RootStackParamList>();
const Tab = createBottomTabNavigator<TabParamList>();
type RootStackParamList = {
Main: undefined;
Profile: { userId: string };
Settings: undefined;
};
function AppNavigator() {
return (
<NavigationContainer linking={linkingConfig}>
<Stack.Navigator>
<Stack.Screen name="Main" component={TabNavigator} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
createNativeStackNavigator використовує нативні анімації (UINavigationController на iOS, Fragment transactions на Android) — це швидше за JS-анімації createStackNavigator.
Deep links через linking prop: { screens: { Profile: 'profile/:userId' } }.
Flutter: GoRouter
GoRouter — офіційно рекомендований пакет для Flutter-навігації з підтримкою web URL та deep links:
final router = GoRouter(
routes: [
GoRoute(path: '/', builder: (ctx, state) => HomeScreen()),
GoRoute(
path: '/profile/:userId',
builder: (ctx, state) => ProfileScreen(userId: state.pathParameters['userId']!),
),
],
);
context.go('/profile/123') — навігація з заміною стека. context.push('/profile/123') — push поверх поточного стека. Працює однаково на iOS, Android та Flutter Web.
Типові проблеми при налаштуванні навігації
Потеря стану при поверненні. На Android при popBackStack Fragment пересоздається. Рішення: FragmentContainerView з saveState = true або ViewModel вище рівня навігації.
Дублювання натискань. Швидкий подвійний тап на кнопку робить подвійний push. На iOS: isMovingToParent перевірка. На Android: currentDestination?.id == R.id.target перед navigate. У React Navigation: navigation.navigate ідемпотентна для одного екрана, navigation.push — ні.
Неправильні анімації на Android. Кастомні enterAnim/exitAnim через Navigation Component — працюють. Через FragmentTransaction.setCustomAnimations безпосередньо з Navigation — ломаються при popBackStack.
Що включає налаштування навігації
Проектування навігаційного графа під функціональні вимоги. Реалізація deep link схеми з парсингом параметрів. Налаштування tab/drawer/modal навігації. Збереження стану стека при переключенні вкладок. Тестування навігаційних сценаріїв.
Строки
Базова навігація (stack + tabs): 2–3 дні. Складна навігація з deep links, auth flow, nested navigators: 5–8 днів. Вартість — після аналізу вимог.







