StackOv has organized with SPM packages. Each package contains some logical part of the app.
This way was described by Apple in the articel - Organizing Your Code with Local Packages.
StackOv.xcodeproj contains the main views of the app with global splitting for iOS and iPadOS platforms. MacOS version is available through the Catalyst.
Flows
contains all flows of the app. Each flow is an autonomous spm package which manage some group of screens (views) which has horizontal navigation. By the way, one flow could contain only one screen (view).Stores
contains all stores of the app. Each store is an autonomous spm package which generally is a connector between the service layer and the view layer.Services
contains all services of the app such as some kind of network package or global managers.Modules
(the virtual group is managed by the XCode) contains all basic packages which help us to make this app.
We use Swinject framework to give each view, store, or service everything that they need.
All dependencies have managed in the AppScript package.
To work with git we use a popular tactic which was described here.
Our repo has the next structure of branches:
main
develop
feature/\d+-.+
bug/\d+-.+
hotfix/.+
Note 1: feature
, bug
braches must have the number of some issue.
Note 2: We use next groups of labels:
- Prority:
Blocker
,High
,Medium
,Low
- Type of device:
Any device
,iPad
,iPhone
,Mac Catalyst
- Type of activity:
Layout
,WIP
,blocked
,help wanted
- Type of issue:
feature
,bug
- Service marks:
Service task
,documenation
,duplicate
,question
,wontfix
Note 3: Don't forget to link your pull request with the issue unless hotfix
pull requests (Github guid).
Note 4: Please, if your local branch of some issues should be updated from some parent branch (develop, master) don't make git merge
because this makes git history so unreadable. The best way is to rebase it to the parent.
To work with git much more cumfortable, you can use Fork app.
Model-View-Intent design pattern. We use paradigm which has provided by MobX js framework.
To know more about what is MVI about, I recommend readin this part of the article.
Long story short, the model layer works with business logic through such as services, managers, or stores. However, stores usually work as the mediator between the view layer and the model layer. View layer subscribes to some store through publishing properties and will be notified when these properties have changed, but this layer cannot change these values directly. To change something inside a store the view layer must makes an action (intent). Consider the next example:
final class PageStore: ObservableObject {
enum State {
case content([Model])
case loading
}
@Published private(set) var state: State = .loading
func fetch() { ... }
}
struct PageView: View {
@Store var store: PageStore
var body: some View {
ScrollView {
switch store.state {
case let .content(model):
content(model)
case .loading:
Text("Loading")
.onAppear { store.fetch() }
}
}
}
}