{"id":13694600,"url":"https://github.com/VAnsimov/MVI-SwiftUI","last_synced_at":"2025-05-03T04:30:35.544Z","repository":{"id":195072285,"uuid":"407038697","full_name":"VAnsimov/MVI-SwiftUI","owner":"VAnsimov","description":null,"archived":false,"fork":false,"pushed_at":"2024-06-13T14:13:35.000Z","size":1515,"stargazers_count":69,"open_issues_count":0,"forks_count":6,"subscribers_count":1,"default_branch":"develop","last_synced_at":"2024-11-12T21:38:56.323Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/VAnsimov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-09-16T05:53:22.000Z","updated_at":"2024-11-06T17:46:37.000Z","dependencies_parsed_at":"2023-09-16T11:43:42.371Z","dependency_job_id":"92ae9ecb-2748-4272-8259-5290442f9121","html_url":"https://github.com/VAnsimov/MVI-SwiftUI","commit_stats":null,"previous_names":["vansimov/mvi-swiftui"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VAnsimov%2FMVI-SwiftUI","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VAnsimov%2FMVI-SwiftUI/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VAnsimov%2FMVI-SwiftUI/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VAnsimov%2FMVI-SwiftUI/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/VAnsimov","download_url":"https://codeload.github.com/VAnsimov/MVI-SwiftUI/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252144292,"owners_count":21701381,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T17:01:35.506Z","updated_at":"2025-05-03T04:30:34.731Z","avatar_url":"https://github.com/VAnsimov.png","language":"Swift","funding_links":[],"categories":["Misc"],"sub_categories":["SwiftUI"],"readme":"# SwiftUI and MVI\n\n#### Publication: [medium.com/@vyacheslavansimov/swiftui-and-mvi-3acac8d4416a](https://medium.com/@vyacheslavansimov/swiftui-and-mvi-3acac8d4416a)\n\n\n## MVI — brief history and principle of operation\n\nThis pattern was first described by JavaScript developer Andre Staltz. The general principles can be found [here](https://staltz.com/unidirectional-user-interface-architectures.html)\n\n![](README_sources/image_001.jpeg)\n\n\n- Intent: function from Observable of user events to Observable of “actions”\n- Model: function from Observable of actions to Observable of state\n- View: function from Observable of state to Observable of rendering\nCustom element: subsection of the rendering which is in itself a UI program. May be implemented as MVI, or as a Web Component. Is optional to use in a View.\n\nMVI has a reactive approach. Each module (function) expects some event, and after receiving and processing it, it passes this event to the next module. It turns out an unidirectional flow.\n\nIn the mobile app the diagram looks very close to the original with only minor changes:\n\n![](README_sources/image_002.png)\n\n- Intent receives an event from View and communicates with the business logic\n- Model receives data from Intent and prepares it for display. The Model also keeps the current state of the View.\n- View displays the prepared data.\n\nTo provide a unidirectional data flow, you need to make sure that the View has a reference to the Intent, the Intent has a reference to the Model, which in turn has a reference to the View.\n\nThe main problem in implementing this approach in SwiftUI is View. View is a structure and Model cannot have references to View. To solve this problem, you can introduce an additional layer Container, which main task is to keep references to Intent and Model, and provide accessibility to the layers so that the unidirectional data flow is truly unidirectional.\nIt sounds complicated, but it is quite simple in practice.\n\n## MVI\n\nContainer is independent of the life cycle of the View because it is @StateObject. Every time the View is reinitialization, Intent and Model remain the same.\n\n![](README_sources/image_003.png)\n\nThere is a unidirectional data flow between the modules.\n\n1) View receives the user's event.\n2) Intent receives an event from View and communicates with the business logic\n3) Model receives data from Intent and prepares it for display. The Model also keeps the current state of the View.\n4) View displays the prepared data.\n\n![](README_sources/image_004.png)\n\n# Templates for Xcode\n\n### xctemplate\n\nThe template can be found in Templates-for-Xcode/xctemplate\n\nAdd the file *.xctemplate to the folder:\n/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/File Templates\n\nThe template can be found in the Xcode menu\nFile -\u003e New -\u003e File...\n\n### Router\n\nThe router is in Swift Package Manager and can be copied and reused in your projects\n\n# How to use Router?\n\n### Implementation Router \n\nBelow is the most complete version, if you don't need something, you don't have to write it.\n\n**Step 1**: Create a `enum` for the list of screens the View will open to. It should implement the `RouterScreenProtocol` protocol.\n\n```swift\n enum SomeRouterScreenType: RouterScreenProtocol {\n\n     case productScreen(id: UUID)\n }\n```\n\n**Step 2**: Create a `enum` for the list of alerts that the View will display. It should implement the `RouterAlertScreenProtocol` protocol.\n\n```swift\nenum SomeRouterAlertType: RouterAlertScreenProtocol {\n\n    case error(title: String, message: String)\n}\n```\n\n**Step 3**: We need to implement RouterModifierProtocol is ViewModifier in your router.\n\n```swift\nstruct SomeRouter: RouterModifierProtocol {\n\n    // If you don't need Alerts, you can use `RouterDefaultAlert`. Example: RouterEvents\u003cSomeRouterScreenType, RouterDefaultAlert\u003e\n    // If you do not need to go to other screens, then use `RouterEmptyScreen`. Example: RouterEvents\u003cRouterEmptyScreen, SomeRouterAlertType\u003e\n    let routerEvents: RouterEvents\u003cSomeRouteScreenType, SomeRouterAlertType\u003e\n}\n```\n\n**Step 4**: Implement the functions getScreenPresentationType(for:), getScreen(for:), onScreenDismiss(type:) in your router\n\n```swift\nextension SomeRouter {\n\n    // Optional\n    func getScreenPresentationType(for type: SomeRouterScreenType) -\u003e RouterScreenPresentationType {\n        .fullScreenCover\n    }\n\n    // Optional\n    @ViewBuilder\n    func getScreen(for type: SomeRouterScreenType) -\u003e some View {\n        switch type {\n        case let .productScreen(id):\n            Text(\"Product Screen View: \\(id.uuidString)\")\n        }\n    }\n\n   // Optional\n    func onScreenDismiss(type: SomeRouterScreenType) {}\n}\n```\n\n**Step 5**: Implement the functions getAlertTitle(for:), getAlertMessage(for:), getAlertActions(for:) in your router\n\n```swift\nextension SomeRouter {\n\n    // Optional\n    func getAlertTitle(for type: SomeRouterAlertType) -\u003e Text? {\n        switch type {\n        case let .error(title, _):\n            Text(title)\n        }\n    }\n\n    // Optional\n    @ViewBuilder\n    func geteAlertMessage(for type: SomeRouterAlertType) -\u003e some View {\n        switch type {\n        case let .error(_, message):\n            Text(message)\n        }\n    }\n\n    // Optional\n    @ViewBuilder\n    func getAlertActions(for type: SomeRouterAlertType) -\u003e some View {\n        Button(\"Yes\", role: .none, action: {\n            ...\n        })\n        Button(\"Cancel\", role: .cancel, action: {})\n    }\n}\n```\n\n### Use Router \n\nHow do I use the router? You can see this in the following example:\n\n\n```swift\nstruct SomeView: View {\n\n    let routerEvents = RouterEvents\u003cSomeRouterScreenType, SomeRouterAlertType\u003e()\n\n    var body: some View {\n        Text(\"Hello, World!\")\n            .modifier(SomeRouter(routerEvents: routerEvents))\n            .onAppear {\n                routerEvents.routeTo(.group(id: UUID()))\n            }\n    }\n}\n```\n\n\n# Maintainers\n\n* [Vyacheslav Ansimov](https://www.linkedin.com/in/vansimov/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FVAnsimov%2FMVI-SwiftUI","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FVAnsimov%2FMVI-SwiftUI","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FVAnsimov%2FMVI-SwiftUI/lists"}