Class
Presenter
A base class for presenters with @Observable view state.
@MainActor class Presenter<ViewControllerType> where ViewControllerType : ViewControllable
Mentioned In
Overview
Presenter is @MainActor-isolated and @Observable. The view controller holds the presenter and SwiftUI reads its stored properties directly (@Bindable locally in body for two-way bindings); UIKit views can observe them via the Observations { ... } macro.
Overview
Presenter is optional in the napkin architecture. There are three shapes a feature commonly takes:
Headless napkin (no view). Conform to Interactable directly; no presenter at all.
Viewful napkin with a presenter object. Subclass Presenter, add @Observable-friendly stored properties, and have it conform to the feature’s Presentable protocol. The view controller renders from the presenter and forwards user events to a listener. Re-annotate subclasses with @Observable so their own stored properties are tracked.
Viewful napkin without a separate presenter. Have the view controller conform to the feature’s Presentable protocol directly, and skip Presenter.
Presenter is the right choice when you have view state worth formatting once and reading from multiple places — display strings, loading flags, computed labels — and you want to keep the view layer thin.
Usage
Define the presentable seam:
protocol HomePresentable: Presentable {
func presentUser(_ user: User) async
}
Implement it as a Presenter subclass:
@MainActor
@Observable
final class HomePresenter: Presenter<HomeViewController>, HomePresentable {
var displayName: String = ""
var isLoggingOut: Bool = false
func presentUser(_ user: User) async {
displayName = "\(user.firstName) \(user.lastName)"
}
}
Read it from SwiftUI:
Hold the presenter weakly — it owns the view controller that owns the view. Rebind with @Bindable inside body for two-way bindings.
struct HomeView: View {
weak var presenter: HomePresenter?
let listener: HomeViewListener
var body: some View {
VStack {
Text(presenter?.displayName ?? "")
Button("Logout") {
dispatch { await listener.didTapLogout() }
}
}
}
}
Read it from UIKit:
final class HomeViewController: UIViewController, HomeViewControllable {
var presenter: HomePresenter!
override func viewDidLoad() {
super.viewDidLoad()
Observations {
nameLabel.text = presenter.displayName
}
}
}
See Also
Presentable
See Also
ViewControllable
Topics
Creating a Presenter
Accessing the View
Relationships
Conforms To