π Multi-tab hosting (UIKit & SwiftUI)
Navigation overview Β· Destination catalogue β every screen you can open with sdk.navigate.
When you embed both Nutrition and Training in your app β typically as two tabs β the SDK gives you a thin host VC per module and a coordinator that wires it to your tab UI. One shared FlutterViewController powers both tabs.
Why this matters: iOS's Flutter engine can only render into one
FlutterViewControllerat a time. NaΓ―ve "two FlutterVCs per engine" embeddings cause freezes when switching tabs. The SDK pattern below avoids it entirely.
UIKit (UITabBarController)β
import UIKit
import AzeooSDK
final class MainTabBarController: UITabBarController {
/// Holding the coordinator alive keeps the SDK's weak ref valid for the
/// lifetime of this tab bar controller.
private var tabCoordinator: AzeooUITabBarCoordinator?
override func viewDidLoad() {
super.viewDidLoad()
setupTabs()
}
private func setupTabs() {
guard let sdk = AzeooSDK.shared else { return }
let status = UINavigationController(rootViewController: StatusViewController())
status.tabBarItem = UITabBarItem(title: "Status",
image: UIImage(systemName: "info.circle"),
selectedImage: nil)
// Tab hosts β thin VCs that contain the shared Flutter surface.
let nutrition = sdk.tabHost(for: .nutrition)
nutrition.tabBarItem = UITabBarItem(title: "Nutrition",
image: UIImage(systemName: "leaf"),
selectedImage: nil)
let training = sdk.tabHost(for: .training)
training.tabBarItem = UITabBarItem(title: "Training",
image: UIImage(systemName: "dumbbell"),
selectedImage: nil)
viewControllers = [status, nutrition, training]
selectedViewController = status
// Install the coordinator once. From now on, any sdk.navigate(...)
// from anywhere automatically flips this tab bar.
let coordinator = AzeooUITabBarCoordinator(self, mapping: [
.nutrition: 1,
.training: 2,
])
sdk.setModuleContainer(coordinator)
tabCoordinator = coordinator
}
deinit {
AzeooSDK.shared?.setModuleContainer(nil)
}
}
That's all the host code you need. From any view controller you can now do:
sdk.navigate(to: .nutrition(.plan(id: "abc-123")))
// Tab bar visually switches to Nutrition.
// Flutter switches to nutrition and pushes the plan detail.
SwiftUI (TabView)β
import SwiftUI
import AzeooSDK
struct ContentView: View {
@EnvironmentObject var sdkManager: SDKManager
@State private var selectedTab = 0
@State private var tabCoordinator: AzeooSwiftUITabCoordinator<Int>? = nil
var body: some View {
TabView(selection: $selectedTab) {
StatusView()
.tabItem { Label("Status", systemImage: "info.circle.fill") }
.tag(0)
sdkManager.sdk!.modules.nutrition.getView()
.tabItem { Label("Nutrition", systemImage: "leaf.fill") }
.tag(1)
sdkManager.sdk!.modules.training.getView()
.tabItem { Label("Training", systemImage: "dumbbell.fill") }
.tag(2)
}
.onAppear {
guard let sdk = sdkManager.sdk, tabCoordinator == nil else { return }
let coordinator = AzeooSwiftUITabCoordinator<Int>(
selection: $selectedTab,
mapping: [.nutrition: 1, .training: 2]
)
sdk.setModuleContainer(coordinator)
tabCoordinator = coordinator
}
.onDisappear {
sdkManager.sdk?.setModuleContainer(nil)
tabCoordinator = nil
}
}
}
How it worksβ
sdk.navigate(to: .training(.workouts))
β
βΌ
ββββββββββββββββββββββββββββββββββββββββ
β AzeooSDK.navigate(to:) β
βββββββββββ¬βββββββββββββββββ¬ββββββββββββ
β β
coordinator.azeoo_showModule(.training)
β β
βΌ βΌ
ββββββββββββββββββββββββ Pigeon to(
β AzeooUITabBarCoord. β "training",
β tabBar.selectedIndex β "workout-plans",
β = 2 β params: nil)
ββββββββββββββββββββββββ β
βΌ
Flutter switches its
internal tab + pushes
the workouts route
The host writes one line at startup; every navigation after that is end-to-end automatic.
Why one FlutterViewControllerβ
The SDK holds a single shared FlutterViewController. Each tabHost(for:) returns a thin container view controller. When a tab becomes visible, the SDK:
- Reparents the shared FlutterVC into the active tab host (one UIKit view move, no engine churn)
- Sends Pigeon
display(module)so Flutter renders the right tab content
AzeooTabHost overrides shouldAutomaticallyForwardAppearanceMethods to false, so tab switches don't trigger viewWillDisappear β viewWillAppear on the FlutterVC. The engine attaches once and stays attached β no flash, no freeze.
Other UIKit containersβ
If you use UINavigationController instead of a tab bar:
sdk.setModuleContainer(AzeooUINavigationCoordinator(
navController,
hostsByModule: [
.nutrition: nutritionVC,
.training: trainingVC,
],
))
For anything else (sidebar, page view, drawer, custom): implement AzeooModuleContainer directly β it's one method. See Module containers.