VVSI is an experimental (very) simple architecture for SwiftUI applications, representing something between TCA (The Composable Architecture) and MVVM. The library offers a structured and predictable approach to managing state and business logic, while maintaining ease of use.
- Separation of concerns β clear separation of UI, state, and business logic
- Predictable data flow β unidirectional data flow makes the application more stable
- Testability β isolated components are easy to test
- Out-of-the-box asynchronicity β support for asynchronous operations using Swift Concurrency
- Native SwiftUI integration β works naturally with the SwiftUI system
dependencies: [
.package(url: "https://github.com/username/VVSI.git", from: "0.0.5")
]
VVSI consists of three main components:
- View β responsible for UI and state display
- ViewState β stores the current state and processes actions
- Interactor β contains business logic and handles side effects
// ListView+State.swift
extension ListView {
struct Options {
let count: Int
let length: Int
}
struct VState: StateProtocol {
var items: [String] = []
}
enum VAction: ActionProtocol {
case add
case remove
case random(Options)
}
enum VNotification: NotificationProtocol {
case error(String)
}
}
// ListView+Interactor.swift
extension ListView {
final class Interactor: ViewStateInteractorProtocol {
typealias S = VState
typealias A = VAction
typealias N = VNotification
let notifications: PassthroughSubject<N, Never> = .init()
let service: Dependencies.Service
init(dependencies: Dependencies = .shared) {
service = dependencies.service
}
@MainActor
func execute(
_ state: @escaping CurrentState<S>,
_ action: VAction,
_ updater: @escaping StateUpdater<S>
) {
switch action {
case .add:
Task.detached { [weak self] in
guard await state().items.count < 5 else {
self?.notifications.send(.error("Max items count is 5"))
return
}
await updater { state in
state.items.append("New item")
}
}
case .remove:
Task.detached {
await updater { state in
if !state.items.isEmpty {
state.items.removeLast()
}
}
}
case .random(let opt):
Task {
let strings = (0..<opt.count).map { _ in randomString(length: opt.length) }
await updater { state in
state.items = strings
}
}
}
}
}
}
// ListView.swift
struct ListView: View {
enum AlertType: Identifiable {
var id: String { "\(self)" }
case error(String)
}
@StateObject
var viewState = ViewState(.init(), Interactor())
@State
private var alertType: AlertType? = nil
var body: some View {
ForEach(viewState.state.items, id: \.self) { item in
Text(item)
}
HStack {
Button {
viewState.trigger(.add)
} label: {
Text("Add")
}
Button {
viewState.trigger(.remove)
} label: {
Text("Remove")
}
Button {
viewState.trigger(.random(.init(count: Int.random(in: 1..<10), length: Int.random(in: 1..<5))))
} label: {
Text("Random")
}
}
.alert(item: $alertType) { item in
switch item {
case .error(let error):
Alert(
title: Text("Error"),
message: Text(error),
dismissButton: .default(Text("Ok"))
)
}
}
.onReceive(viewState.notifications) { notification in
switch notification {
case .error(let message):
alertType = .error(message)
}
}
}
}
- StateProtocol β protocol for state structures
- ActionProtocol β protocol for action enumerations
- NotificationProtocol β protocol for notifications from interactor to view
- ViewStateInteractorProtocol β protocol for interactors handling business logic
A complete implementation example is available in the VVSIExample directory.
This project is released under the MIT license. See LICENSE for details.
We welcome your suggestions and contributions to the project! Create Issues and Pull Requests.