{"id":399147,"date":"2024-06-29T14:30:03","date_gmt":"2024-06-29T14:30:03","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=399147"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=399147","title":{"rendered":"<span>SwiftUI and MVI<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>UIKit first appeared in iOS 2, and it is still here. Eventually we got to know it well and learned how to work with it. We have found many architectural approaches. MVVM, the most popular architecture in my opinion, has strengthened its position with the release of SwiftUI, while other architectures seemed to have some kind of problematic relationships with SwiftUI.<\/p>\n<p>But what if I told you that Clean Swift,\u00a0VIPER and other approaches can be adapted to SwiftUI. What if I told you that there are some modern architectures which might be as good as MVVM or even better.<\/p>\n<p>We will talk about MVI.<\/p>\n<p>I will break down what it is and use it as an example to show how you can adapt architectures to make them friendly with SwiftUI.<\/p>\n<hr\/>\n<p>The updated version of the MVI is <a href=\"https:\/\/habr.com\/ru\/articles\/821591\/\" rel=\"noopener noreferrer nofollow\">here<\/a><\/p>\n<hr\/>\n<h2>Bidirectional and Unidirectional architectures<\/h2>\n<p>All known architectures can be divided into two types:<\/p>\n<ol>\n<li>\n<p>Bidirectional<\/p>\n<\/li>\n<li>\n<p>Unidirectional<\/p>\n<\/li>\n<\/ol>\n<p><strong>Bidirectional<\/strong>\u00a0architectures are divided into layers that transmit with each other. One layer can transmit and receive data from another layer.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/21b\/84b\/13d\/21b84b13dc396f9a52672ead0ca25cd3.png\" width=\"2014\" height=\"1004\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/21b\/84b\/13d\/21b84b13dc396f9a52672ead0ca25cd3.png\"\/><\/figure>\n<p>The main drawback of such architectures is the management of data flow. In large and complex screens it is difficult to navigate and almost impossible to keep in mind where the data came from, where it changes, and what the screen finally receives. These architectures are good for small to medium sized applications. They are generally simpler than Unidirectional architectures.<\/p>\n<p><strong>Unidirectional<\/strong>\u00a0architectures are divided into layers that transmit data to one type of layers and receive data from another type.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/038\/fe0\/fbb\/038fe0fbb997399dd304913f191c1ee8.png\" width=\"2030\" height=\"992\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/038\/fe0\/fbb\/038fe0fbb997399dd304913f191c1ee8.png\"\/><\/figure>\n<p>Working with Unidirectional architecture may often cause the complaint, that some layers are excessive on simple screens, but you have to keep them. Another complaint is that with small changes you have to proxy data through all layers. All these disadvantages are compensated on large screens with complex logic. In such architectures duties are distributed better than in Bidirectional architectures. Working with code is simplified because it is easy to track where the data comes from, where it changes and where it goes away.<\/p>\n<p>I was a bit unfair when I said at the beginning that there are architectures as good as MVVM or even better. They do exist, but work best for large projects. I\u2019m talking about MVI, Clean Swift and other Unidirectional approaches.<\/p>\n<p>Let\u2019s take a closer look at one of these architectures.<\/p>\n<h2>MVI \u2014 brief history and principle of operation<\/h2>\n<p>This pattern was first described by JavaScript developer Andre Staltz. The general principles can be found\u00a0<a href=\"https:\/\/staltz.com\/unidirectional-user-interface-architectures.html\" rel=\"noopener noreferrer nofollow\"><u>here<\/u><\/a><\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/f83\/c87\/c3a\/f83c87c3adfd60dc3d5d8a3f8ef97005.jpeg\" width=\"1280\" height=\"960\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f83\/c87\/c3a\/f83c87c3adfd60dc3d5d8a3f8ef97005.jpeg\" data-blurred=\"true\"\/><\/figure>\n<ul>\n<li>\n<p><strong>Intent<\/strong>: function from Observable of user events to Observable of \u201cactions\u201d<\/p>\n<\/li>\n<li>\n<p><strong>Model<\/strong>: function from Observable of actions to Observable of state<\/p>\n<\/li>\n<li>\n<p><strong>View<\/strong>: function from Observable of state to Observable of rendering<\/p>\n<\/li>\n<li>\n<p><strong>Custom element<\/strong>: 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.<\/p>\n<\/li>\n<\/ul>\n<p>MVI 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.<\/p>\n<p>In the mobile app the diagram looks very close to the original with only minor changes:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/8d9\/181\/214\/8d91812145d1aaa5f1f990abbd7aa8a3.png\" width=\"1860\" height=\"1354\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8d9\/181\/214\/8d91812145d1aaa5f1f990abbd7aa8a3.png\"\/><\/figure>\n<ul>\n<li>\n<p><strong>Intent<\/strong>\u00a0receives an event from View and communicates with the business logic<\/p>\n<\/li>\n<li>\n<p><strong>Model<\/strong>\u00a0receives data from Intent and prepares it for display. The Model also keeps the current state of the View<\/p>\n<\/li>\n<li>\n<p><strong>View<\/strong>\u00a0displays the prepared data.<\/p>\n<\/li>\n<\/ul>\n<p>To 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.<\/p>\n<p>The 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.<\/p>\n<p>It sounds complicated, but it is quite simple in practice.<\/p>\n<h2>Implementation of Container<\/h2>\n<p>Let\u2019s write a screen that shows a small list of WWDC videos. I will describe the basics, and you can see the full implementation on\u00a0<a href=\"https:\/\/github.com\/VAnsimov\/MVI-SwiftUI\" rel=\"noopener noreferrer nofollow\"><u>GitHub<\/u><\/a>.<\/p>\n<p>Let\u2019s start with Container, and since this class will be used frequently, we will write a universal class for all screens<\/p>\n<pre><code class=\"swift\">\/\/ 1 final class MVIContainer&lt;Intent, Model>: ObservableObject {      \/\/ 2     let intent: Intent     let model: Model      private var cancellable: Set&lt;AnyCancellable> = []      init(intent: Intent, model: Model, modelChangePublisher: ObjectWillChangePublisher) {         self.intent = intent         self.model = model          \/\/ 3         modelChangePublisher             .receive(on: RunLoop.main)             .sink(receiveValue: objectWillChange.send)             .store(in: &amp;cancellable)     } }<\/code><\/pre>\n<ol>\n<li>\n<p>A class that takes two universal types Intent and Model as input.<\/p>\n<\/li>\n<li>\n<p>References to Intent and to Model. For the universal type Model, we will have a protocol, which will give access to properties and hide functions. I\u2019ll tell you about it later.<\/p>\n<\/li>\n<li>\n<p>It\u2019s necessary to ensure that changes in the Model will receive View, and not Container.<\/p>\n<\/li>\n<\/ol>\n<p>Creating the Container and View will look like this:<\/p>\n<pre><code class=\"swift\">extension ListView {      static func build() -> some View {         let model = ListModel()         let intent = ListIntent(model: model)          let container = MVIContainer(             intent: intent,             model: model as ListModelStatePotocol,             modelChangePublisher: model.objectWillChange)          return ListView(container: container)     } }<\/code><\/pre>\n<p>Let\u2019s see Container in action in View:<\/p>\n<pre><code class=\"swift\">struct ListView: View {      \/\/ 1     @StateObject private var container: MVIContainer&lt;ListIntent, ListModelStatePotocol>      var body: some View {         \/\/ 2         Text(container.model.text)             .padding()             .onAppear(perform: {                 \/\/ 3                 self.container.intent.viewOnAppear()             })     } }<\/code><\/pre>\n<ol>\n<li>\n<p>Container:<\/p>\n<\/li>\n<\/ol>\n<ul>\n<li>\n<p>ListModel is a Model, it performs the logic of data preparation and holds all the properties of the View. View communicates with Model through the ListModelStateProtocol, which hides the logic and gives access only to the properties<\/p>\n<\/li>\n<li>\n<p>@StateObject is needed so that the container together with the Intent and the Model are not recreated when recreating the View<\/p>\n<\/li>\n<\/ul>\n<p>\u00a0\u00a02.\u00a0Gets actual data from Model via container<\/p>\n<p>\u00a0\u00a03.\u00a0Notifies Intent about events via container<\/p>\n<p>To avoid constantly writing container.model and container.intent, you can add these lines:<\/p>\n<pre><code class=\"swift\">private var intent: ListIntent { container.intent } private var properties: ListModelStatePotocol { container.model }<\/code><\/pre>\n<p>In a similar way, through Container you can adapt Clean Swift, VIPER.<\/p>\n<h2>Intent<\/h2>\n<p>Intent waits for events from View for further actions. It works with business logic and databases, makes requests to the server, etc.<\/p>\n<pre><code class=\"swift\">final class ListIntent {      \/\/ 1     private weak var model: ListModelActionsProtocol?      init(model: ListModelActionsProtocol) {         self.model = model     }      func viewOnAppear() {         let number = Int.random(in: 0 ..&lt; 100)          \/\/ 2         model?.parse(number: number)     } }<\/code><\/pre>\n<ol>\n<li>\n<p>ListModelActionsProtocol is another protocol to which Model subscribes, this protocol hides properties for View from Intent.<\/p>\n<\/li>\n<li>\n<p>After Intent receives an event from the View, it receives data synchronously or asynchronously and passes it to the Model<\/p>\n<\/li>\n<\/ol>\n<h2>Model<\/h2>\n<p>Model receives data from Intent and prepares it for display. The Model also keeps the current state of the screen.<\/p>\n<p>The Model protocols we\u2019ve already seen:<\/p>\n<pre><code class=\"swift\">\/\/ 1 protocol ListModelStatePotocol {     var text: String { get } }  \/\/ 2 protocol ListModelActionsProtocol: AnyObject {     func parse(number: Int) }<\/code><\/pre>\n<ol>\n<li>\n<p>ListModelStatePotocol. Through this protocol the View communicates with the Model. There are only properties of the View<\/p>\n<\/li>\n<li>\n<p>ListModelActionsProtocol. Through this protocol Intent communicates with the Model. There are only functions.<\/p>\n<\/li>\n<\/ol>\n<p>Model Implementation:<\/p>\n<pre><code class=\"swift\">\/\/ 1 final class ListModel: ObservableObject, ListModelStatePotocol {     @Published var text: String = \"\" }  \/\/ 2 extension ListModel: ListModelActionsProtocol {      func parse(number: Int) {         text = \"Random number: \" + String(number)     } }<\/code><\/pre>\n<ol>\n<li>\n<p>To use the full power of SwiftUI, let\u2019s sign the Model under the protocol ObservableObject and when we change any property marked as @Published all the changes will automatically receive the View and display it.<\/p>\n<\/li>\n<li>\n<p>Here is the logic to prepare the data for display<\/p>\n<\/li>\n<\/ol>\n<h2>Conclusion<\/h2>\n<p>I decided not to write a lot of unnecessary code and instead described the principle of operation. In more detail you can see it\u00a0<a href=\"https:\/\/github.com\/VAnsimov\/MVI-SwiftUI\" rel=\"noopener noreferrer nofollow\"><u>here<\/u><\/a>.<\/p>\n<p>SwiftUI, like MVI, is built around reactivity, so they fit together well. MVI allows you to implement complex screens and change the state of the screen very dynamically and with minimal effort. This implementation, of course, is not the only correct one, there are always alternatives. However, the pattern fits nicely with Apple\u2019s new UI approach. One class for all screen states makes it much easier to work with the screen.<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/583376\/\"> https:\/\/habr.com\/ru\/articles\/583376\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>UIKit first appeared in iOS 2, and it is still here. Eventually we got to know it well and learned how to work with it. We have found many architectural approaches. MVVM, the most popular architecture in my opinion, has strengthened its position with the release of SwiftUI, while other architectures seemed to have some kind of problematic relationships with SwiftUI.<\/p>\n<p>But what if I told you that Clean Swift,\u00a0VIPER and other approaches can be adapted to SwiftUI. What if I told you that there are some modern architectures which might be as good as MVVM or even better.<\/p>\n<p>We will talk about MVI.<\/p>\n<p>I will break down what it is and use it as an example to show how you can adapt architectures to make them friendly with SwiftUI.<\/p>\n<hr\/>\n<p>The updated version of the MVI is <a href=\"https:\/\/habr.com\/ru\/articles\/821591\/\" rel=\"noopener noreferrer nofollow\">here<\/a><\/p>\n<hr\/>\n<h2>Bidirectional and Unidirectional architectures<\/h2>\n<p>All known architectures can be divided into two types:<\/p>\n<ol>\n<li>\n<p>Bidirectional<\/p>\n<\/li>\n<li>\n<p>Unidirectional<\/p>\n<\/li>\n<\/ol>\n<p><strong>Bidirectional<\/strong>\u00a0architectures are divided into layers that transmit with each other. One layer can transmit and receive data from another layer.<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>The main drawback of such architectures is the management of data flow. In large and complex screens it is difficult to navigate and almost impossible to keep in mind where the data came from, where it changes, and what the screen finally receives. These architectures are good for small to medium sized applications. They are generally simpler than Unidirectional architectures.<\/p>\n<p><strong>Unidirectional<\/strong>\u00a0architectures are divided into layers that transmit data to one type of layers and receive data from another type.<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>Working with Unidirectional architecture may often cause the complaint, that some layers are excessive on simple screens, but you have to keep them. Another complaint is that with small changes you have to proxy data through all layers. All these disadvantages are compensated on large screens with complex logic. In such architectures duties are distributed better than in Bidirectional architectures. Working with code is simplified because it is easy to track where the data comes from, where it changes and where it goes away.<\/p>\n<p>I was a bit unfair when I said at the beginning that there are architectures as good as MVVM or even better. They do exist, but work best for large projects. I\u2019m talking about MVI, Clean Swift and other Unidirectional approaches.<\/p>\n<p>Let\u2019s take a closer look at one of these architectures.<\/p>\n<h2>MVI \u2014 brief history and principle of operation<\/h2>\n<p>This pattern was first described by JavaScript developer Andre Staltz. The general principles can be found\u00a0<a href=\"https:\/\/staltz.com\/unidirectional-user-interface-architectures.html\" rel=\"noopener noreferrer nofollow\"><u>here<\/u><\/a><\/p>\n<figure class=\"full-width\"><\/figure>\n<ul>\n<li>\n<p><strong>Intent<\/strong>: function from Observable of user events to Observable of \u201cactions\u201d<\/p>\n<\/li>\n<li>\n<p><strong>Model<\/strong>: function from Observable of actions to Observable of state<\/p>\n<\/li>\n<li>\n<p><strong>View<\/strong>: function from Observable of state to Observable of rendering<\/p>\n<\/li>\n<li>\n<p><strong>Custom element<\/strong>: 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.<\/p>\n<\/li>\n<\/ul>\n<p>MVI 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.<\/p>\n<p>In the mobile app the diagram looks very close to the original with only minor changes:<\/p>\n<figure class=\"full-width\"><\/figure>\n<ul>\n<li>\n<p><strong>Intent<\/strong>\u00a0receives an event from View and communicates with the business logic<\/p>\n<\/li>\n<li>\n<p><strong>Model<\/strong>\u00a0receives data from Intent and prepares it for display. The Model also keeps the current state of the View<\/p>\n<\/li>\n<li>\n<p><strong>View<\/strong>\u00a0displays the prepared data.<\/p>\n<\/li>\n<\/ul>\n<p>To 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.<\/p>\n<p>The 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.<\/p>\n<p>It sounds complicated, but it is quite simple in practice.<\/p>\n<h2>Implementation of Container<\/h2>\n<p>Let\u2019s write a screen that shows a small list of WWDC videos. I will describe the basics, and you can see the full implementation on\u00a0<a href=\"https:\/\/github.com\/VAnsimov\/MVI-SwiftUI\" rel=\"noopener noreferrer nofollow\"><u>GitHub<\/u><\/a>.<\/p>\n<p>Let\u2019s start with Container, and since this class will be used frequently, we will write a universal class for all screens<\/p>\n<pre><code class=\"swift\">\/\/ 1 final class MVIContainer&lt;Intent, Model>: ObservableObject {      \/\/ 2     let intent: Intent     let model: Model      private var cancellable: Set&lt;AnyCancellable> = []      init(intent: Intent, model: Model, modelChangePublisher: ObjectWillChangePublisher) {         self.intent = intent         self.model = model          \/\/ 3         modelChangePublisher             .receive(on: RunLoop.main)             .sink(receiveValue: objectWillChange.send)             .store(in: &amp;cancellable)     } }<\/code><\/pre>\n<ol>\n<li>\n<p>A class that takes two universal types Intent and Model as input.<\/p>\n<\/li>\n<li>\n<p>References to Intent and to Model. For the universal type Model, we will have a protocol, which will give access to properties and hide functions. I\u2019ll tell you about it later.<\/p>\n<\/li>\n<li>\n<p>It\u2019s necessary to ensure that changes in the Model will receive View, and not Container.<\/p>\n<\/li>\n<\/ol>\n<p>Creating the Container and View will look like this:<\/p>\n<pre><code class=\"swift\">extension ListView {      static func build() -> some View {         let model = ListModel()         let intent = ListIntent(model: model)          let container = MVIContainer(             intent: intent,             model: model as ListModelStatePotocol,             modelChangePublisher: model.objectWillChange)          return ListView(container: container)     } }<\/code><\/pre>\n<p>Let\u2019s see Container in action in View:<\/p>\n<pre><code class=\"swift\">struct ListView: View {      \/\/ 1     @StateObject private var container: MVIContainer&lt;ListIntent, ListModelStatePotocol>      var body: some View {         \/\/ 2         Text(container.model.text)             .padding()             .onAppear(perform: {                 \/\/ 3                 self.container.intent.viewOnAppear()             })     } }<\/code><\/pre>\n<ol>\n<li>\n<p>Container:<\/p>\n<\/li>\n<\/ol>\n<ul>\n<li>\n<p>ListModel is a Model, it performs the logic of data preparation and holds all the properties of the View. View communicates with Model through the ListModelStateProtocol, which hides the logic and gives access only to the properties<\/p>\n<\/li>\n<li>\n<p>@StateObject is needed so that the container together with the Intent and the Model are not recreated when recreating the View<\/p>\n<\/li>\n<\/ul>\n<p>\u00a0\u00a02.\u00a0Gets actual data from Model via container<\/p>\n<p>\u00a0\u00a03.\u00a0Notifies Intent about events via container<\/p>\n<p>To avoid constantly writing container.model and container.intent, you can add these lines:<\/p>\n<pre><code class=\"swift\">private var intent: ListIntent { container.intent } private var properties: ListModelStatePotocol { container.model }<\/code><\/pre>\n<p>In a similar way, through Container you can adapt Clean Swift, VIPER.<\/p>\n<h2>Intent<\/h2>\n<p>Intent waits for events from View for further actions. It works with business logic and databases, makes requests to the server, etc.<\/p>\n<pre><code class=\"swift\">final class ListIntent {      \/\/ 1     private weak var model: ListModelActionsProtocol?      init(model: ListModelActionsProtocol) {         self.model = model     }      func viewOnAppear() {         let number = Int.random(in: 0 ..&lt; 100)          \/\/ 2         model?.parse(number: number)     } }<\/code><\/pre>\n<ol>\n<li>\n<p>ListModelActionsProtocol is another protocol to which Model subscribes, this protocol hides properties for View from Intent.<\/p>\n<\/li>\n<li>\n<p>After Intent receives an event from the View, it receives data synchronously or asynchronously and passes it to the Model<\/p>\n<\/li>\n<\/ol>\n<h2>Model<\/h2>\n<p>Model receives data from Intent and prepares it for display. The Model also keeps the current state of the screen.<\/p>\n<p>The Model protocols we\u2019ve already seen:<\/p>\n<pre><code class=\"swift\">\/\/ 1 protocol ListModelStatePotocol {     var text: String { get } }  \/\/ 2 protocol ListModelActionsProtocol: AnyObject {     func parse(number: Int) }<\/code><\/pre>\n<ol>\n<li>\n<p>ListModelStatePotocol. Through this protocol the View communicates with the Model. There are only properties of the View<\/p>\n<\/li>\n<li>\n<p>ListModelActionsProtocol. Through this protocol Intent communicates with the Model. There are only functions.<\/p>\n<\/li>\n<\/ol>\n<p>Model Implementation:<\/p>\n<pre><code class=\"swift\">\/\/ 1 final class ListModel: ObservableObject, ListModelStatePotocol {     @Published var text: String = \"\" }  \/\/ 2 extension ListModel: ListModelActionsProtocol {      func parse(number: Int) {         text = \"Random number: \" + String(number)     } }<\/code><\/pre>\n<ol>\n<li>\n<p>To use the full power of SwiftUI, let\u2019s sign the Model under the protocol ObservableObject and when we change any property marked as @Published all the changes will automatically receive the View and display it.<\/p>\n<\/li>\n<li>\n<p>Here is the logic to prepare the data for display<\/p>\n<\/li>\n<\/ol>\n<h2>Conclusion<\/h2>\n<p>I decided not to write a lot of unnecessary code and instead described the principle of operation. In more detail you can see it\u00a0<a href=\"https:\/\/github.com\/VAnsimov\/MVI-SwiftUI\" rel=\"noopener noreferrer nofollow\"><u>here<\/u><\/a>.<\/p>\n<p>SwiftUI, like MVI, is built around reactivity, so they fit together well. MVI allows you to implement complex screens and change the state of the screen very dynamically and with minimal effort. This implementation, of course, is not the only correct one, there are always alternatives. However, the pattern fits nicely with Apple\u2019s new UI approach. One class for all screen states makes it much easier to work with the screen.<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/583376\/\"> https:\/\/habr.com\/ru\/articles\/583376\/<\/a><br \/><\/br><\/br><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-399147","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/399147","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=399147"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/399147\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=399147"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=399147"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=399147"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}