Part two of the microapps series splits modules into two kinds. Foundation modules, such as the design system or networking layer, are imported everywhere. Feature modules each implement one complete product feature and can ship alone as a microapp.
Declaring a Feature Module
A feature module is one more target in the shared package, depending on the foundation modules it needs.
let package = Package(
name: "MyAppLibrary",
platforms: [.iOS(.v15)],
products: [
.library(name: "DesignSystem", targets: ["DesignSystem"]),
.library(name: "Onboarding", targets: ["Onboarding"])
],
targets: [
.target(name: "DesignSystem"),
.target(name: "Onboarding", dependencies: ["DesignSystem"]),
]
)
The module's public surface needs explicit public types and initializers, because memberwise
initializers are internal by default:
public struct OnboardingItem: Hashable {
let systemImage: String
let title: String
let body: String
public init(systemImage: String, title: String, body: String) {
self.systemImage = systemImage
self.title = title
self.body = body
}
}
The view imports the foundation module for shared styles, for example import DesignSystem and
.buttonStyle(.main) on its call-to-action button.
The App Target Is a Thin Coordinator
All feature logic lives in the modules. The app target only instantiates features and navigates between them.
import SwiftUI
import Onboarding
import DailySummary
struct RootView: View {
@AppStorage("isFirstLaunch")
var isFirstLaunch: Bool = true
var body: some View {
NavigationView {
DailySummaryView(date: .now)
}
.sheet(isPresented: $isFirstLaunch) {
OnboardingView(items: [
.init(systemImage: "pills", title: "Pills", body: "Track your pills"),
.init(systemImage: "heart", title: "Monitor", body: "Monitor your heart")
]) {
isFirstLaunch = false
}
}
}
}
A feature module can span several screens, for example a whole checkout flow. Because it is decoupled, a team can wrap it in a tiny host app and ship it through TestFlight for early QA feedback, without waiting for the rest of the product.