graph TD
D[Deep link]-->R
A[App Delegate]-->R
R{Router} -->V(View)
P(Presenter) -- View Model --> V(View)
V -- Event Delegate --> P
P --> U1((Use Case))
U1-.->P
U1-->S[Persistent Storage]
S-. Model Change Notification .->P
U1-->A1>API Request]
A1-.->U1
If a feature is named Dashboard
, the folder structure in the XCode project would like example below. If the view is more complicated, there may be other .xib and .swift files.
Dashboard ├── DashboardProtocols.swift ├── DashboardPresenter.swift ├── DashboardViewController.swift ├── DashboardViewController.storyboard └── ...
DashboardViewController will conform to DashboardViewProtocol
.
- Entry point for Router
- Creates the Presenter and has a strong reference to it
DashboardPresenter will conform to DashbboardPresenterProtocol
.
- Has a weak reference to the View
sequenceDiagram
Router->>View: create with params
View->>View: create Presenter
View->>Presenter: set me as your View
View->>Router: UIViewController
Router->>View: Router.show()
View->>View: viewDidLoad()
View->>Presenter: viewIsReady()
Note over Presenter: load the data with params
Presenter->>Use Cases: request
Use Cases->>API: API request
Presenter-->>Storage: (listen for model changes)
View->>Presenter: pageViewStarted()
Use Cases-->>Presenter: status
API-->>Use Cases: API response
Use Cases->>Use Cases: parse response, handle errors
Use Cases-->>Presenter: status
Use Cases->>Storage: Persist and notify
Storage-->>Presenter: received model change notification
Presenter->>Presenter: fetch entities from Storage
Presenter->>Presenter: transform to view model
Presenter->>View: update display with view model
Note over View: Some time later...
View->>Presenter: listItemWasTapped(id)
Presenter->>Router: Router.route("/details", id)
Note over View: ...or...
View->>Presenter: refreshControlChanged()
Presenter->>Use Cases: (same as before)
graph TD
A[App Delegate]-->R[Router]
R{Router} -->P1(Presenter)
D[Deep link]-->R
The Router is app specific and is not shared between our different apps. The Router will contain a dictionary of routes. A global singleton instance named router
is declared in the AppDelegate.
The Router knows about all of the Presenters and their maker functions. The Router will receive arguments from the callers of the routing action. It will use pass these arguments to the Presenters' maker functions.
graph TD
R{Router} -->P1(Presenter A)
R-->P2(Presenter B)
R-->P3(Presenter C)
R-->P4(Presenter D)
P2-. route to C .->R
The Router knows about all of the Presenters and their maker functions. The Router will receive arguments from the callers of the routing action. It will pass these arguments to the Presenters' maker functions.
The Presenter is the entry point for the Router. Router, navigation controllers, UIViewControllers, and other Presenters do not instantiate a View directly. For example, in the diagram above, Presenter B
receives a user event from its View . The proper action is to show Presenter C
. Instead of instantiating Presenter C
directly, it will ask the Router to do it. This flow will ensure that Presenters are not tightly coupled to each other.
Each component's Presenter will conform to the PresenterProtocol and expose a var viewController: UIViewController
computed property that the Router can present modally or push onto the navigation stack.
The Presenter should provide static maker function(s) that take all the needed arguments (usually in the form of entity ids) to request all the data required to populate the View's display controls. These static maker functions should also allow for dependency injection to ensure that they can be fully mocked and tested.
TODO: Example maker/create function?
graph TD
P(Presenter) -- View Model --> V(View)
V -. Event Delegate .-> P
V -->G(UIViewController)
G-->E[Storyboard]
G-->F[Xibs]
P --> C(Coordinator)
A Presenter can have 1 and only 1 View.
graph TD
P(Presenter) --> V(View)
P --> C(Coordinator)
Coordinators are optional. A Presenter can have 0 or 1 Coordinator. In cases where the View has no data requirements or is only dependent on 1 or 2 Use Cases, the Presenter can do the work of the Coordinator. As far as we can tell from our initial design meetings, these low-data backed views are rare; however, we wanted leave it as an option to exclude a Coordinator (thus its associated protocols and tests) when appropriate. This optionality may be removed once we have more experience implementing this architecture.
graph TD
P(Presenter) --> V(View)
P --> C(Coordinator)
C --> U1((Use Case))
U1-. Status .->C
U1-->A1>API Request]
A1-.->U1
C --> U2((Use Case))
U2-. Status .->C
U2-->A2>API Request]
A2-.->U2
C --> U3((Use Case))
U3-. Status .->C
U3-->A3>API Request]
A3-.->U3
A Coordinator will aggregate (or coordinate) 1 to many Use Cases in order to fulfill the data requirements needed by the UI layers.
The model layer consist of Models, Use Cases, API Requests, and Persistent Storage.
graph TD
C(Coordinator) --> U1((Use Case))
U1-->S(Persistent Storage)
U1-->A1>API Request]
A1-.->U1
graph TD
P(Presenter) --> V(View)
P --> C(Coordinator)
C --> U1((Use Case))
U1-->S(Persistent Storage)
S-. Model Change Notification .->P