<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Nick McConnell on Medium]]></title>
        <description><![CDATA[Stories by Nick McConnell on Medium]]></description>
        <link>https://medium.com/@nicmcconn?source=rss-c5b0b1aec01a------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*uRozPheKWGmxR3vq.</url>
            <title>Stories by Nick McConnell on Medium</title>
            <link>https://medium.com/@nicmcconn?source=rss-c5b0b1aec01a------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 30 Apr 2026 14:19:25 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@nicmcconn/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[iOS Localization: LocalizedStringResource vs LocalizedStringKey vs String]]></title>
            <link>https://levelup.gitconnected.com/ios-localization-localizedstringresource-vs-localizedstringkey-vs-string-56cb519cf098?source=rss-c5b0b1aec01a------2</link>
            <guid isPermaLink="false">https://medium.com/p/56cb519cf098</guid>
            <category><![CDATA[localization]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[modularization]]></category>
            <category><![CDATA[string]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Nick McConnell]]></dc:creator>
            <pubDate>Mon, 16 Sep 2024 04:58:33 GMT</pubDate>
            <atom:updated>2024-11-11T02:25:20.963Z</atom:updated>
            <content:encoded><![CDATA[<h4>iOS Localization</h4><h3>LocalizedStringResource vs LocalizedStringKey vs String</h3><h4>Deciding how to pass around localized strings in Swift</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*osmFzTt72I17Ym4jWhOKEA.jpeg" /></figure><p>I’ve recently been re-reviewing the <a href="https://developer.apple.com/wwdc23/10155">strings catalog WWDC video</a> as part of both a localization talk at <a href="https://www.iosdevuk.com/">iOSDevUK</a> and researching a migration from the legacy NSLocalizedString.</p><p>In the video, I came across <a href="https://developer.apple.com/wwdc23/10155?time=519">this statement</a> from Apple:</p><blockquote><em>LocalizedStringResource is the recommended type for representing and passing around localizable strings.</em></blockquote><p>The implications here feel massive. Most strings in a localized app are strings that are displayed to the user and therefore require localization. Is Apple recommending that we should be replacing most of our use of String with LocalizedStringResource? And, for a change as big as this, I would like more details and clear benefits.</p><p>Apple doesn’t answer this. So I tried to answer this question myself and that research took me down a couple of unexpected paths which we will get into shortly.</p><p>Firstly though let&#39;s start with a simple UI component with a simple String input type. I&#39;m trying to represent something typical here but simplified. If the string should always be localized, is there a better way of enforcing or indicating this expectation?</p><pre>struct MyView: View {<br>  let input: String<br>  var body: some View {<br>    Text(input)<br>  }<br>}<br><br>// Somewhere else<br>let view = MyView(input: &quot;some thing&quot;)</pre><h3>LocalizedStringKey</h3><p>Before LocalizedStringResource (which became available in iOS 16) there was LocalizedStringKey. This type has been deeply integrated with SwiftUI from the beginning, implicitly being used as the default type in many components and modifiers:</p><pre>Text(“a key”)<br>TextView(&quot;a key&quot;, text: $text)<br>Toggle(&quot;a key&quot;, isOn: $isOn)<br>SomeView().navigationTitle(&quot;a key&quot;)</pre><p>These components each have equivalent StringProtocol pairs where a String can be passed in instead of a StringLocalizedKey but for literals as above StringLocalizedKey is the default type because of @_disfavoredOverload on the String initializer. (Note — don&#39;t do this in your code - Apple would prefer you didn&#39;t. See <a href="https://github.com/swiftlang/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md">here</a>.)</p><p>So let&#39;s update our UI component:</p><pre>struct MyView: View {<br>  let input: LocalizedStringKey<br>  var body: some View {<br>    Text(input)<br>  }<br>}<br><br>// Somewhere else<br>let view = MyView(input: &quot;a key&quot;)</pre><p>This seems to work — “a key” is extracted during build-time into a string catalog (if you have one) and a look-up is done to find a translation at run time. It feels more type-safe than a string and works well with SwiftUI.</p><p>However, there are limitations here. To understand more, we have to delve into keys, default values, comments, and bundles.</p><h4>Keys, Default Values and Comments</h4><p>Apple is fond of showing how easy it is to localize:</p><pre>Text(“I am localization ready”)</pre><p>And it’s true, Apple made SwiftUI with localization top of mind. However, there are limitations to the approach of using the default language as the key in localization. Words used in different contexts can have different translations (e.g. the word “book” — see <a href="https://developer.apple.com/documentation/foundation/nslocalizedstringwithdefaultvalue">here</a>) and translation processes work more accurately when the context is uniquely identified.</p><p>Thus, it is common to create a keying format along the lines of [flow].[screen].[element].[usage] (example: login.password_entry.password.label for a login flow&#39;s password label). A comment can also be added to help give the translator additional context:</p><pre>Text(“login.password_entry_password.label”, comment: “a short label for the password data entry field”)</pre><p>In this case, we would have to add the default language value of the key in the strings catalog directly after this string is extracted. Unfortunately, this requires context-switching while coding, something I’d rather avoid.</p><p>It’s also important to note that if we added a comment to our UI component above, the comment would be completely ignored by the extraction process which is not immediately apparent:</p><pre>struct MyView: View {<br>  let input: LocalizedStringKey<br>  var body: some View {<br>    Text(input, comment: &quot;this is not picked up by extraction&quot;)<br>  }<br>}</pre><h4>Bundle</h4><p>Bundle is one of the optional parameters of Text - the default is .main . Why do we need a bundle? In all but simple code bases, the preferred approach is to separate code into modules - preferably using Swift Package Manager. Each module has its own string catalog (and bundle) - we use the.module bundle. String extraction, export/import, and run-time lookup all work accordingly.</p><p>In the case of our UI component above, we will have a problem. If the calling code is a module (that is not the main target) and the component is in yet another module — the run-time localization lookup will fail. The process needs to know which bundle so simply passing a key isn’t enough.</p><h4>Strings that are already localized</h4><p>A common example here is backend strings that in most applications are translated before being sent to the app. Trying to pass in a String from a backend model into a component that requires a LocalizedStringKey will force an error. Other than creating dual initializers for the component, the solution would :</p><pre>MyView(input: LocalizedStringKey(stringLiteral: myBackendString))</pre><p>However, it should be noted that the run-time localization look-up will still be attempted even though the string is already localized which probably would not cause issues, but it’s worth noting. I could not find a way of avoiding this.</p><h4>LocalizedStringKey — Summary</h4><p>✅ SwiftUI integration<br>✅ Stronger typing than String<br>❌ Doesn’t allow in-line default value<br>❌ Comment and table disconnected from key and sometimes ignored<br>❌ Module boundary issues<br>❌ Awkward for backend strings (require conversion, will still do look-up)</p><h3>LocalizedStringResource</h3><p>So now let’s look at what Apple seems to recommend. LocalizedStringResource has been available since iOS 16 and in many ways solves the limitations of LocalizedStringKey but grouping the key, the default value, the bundle, and the comment into one.</p><pre>struct MyView: View {<br>  let input: LocalizedStringResource<br>  var body: some View {<br>    Text(input)<br>  }<br>}<br><br>// Somewhere else or some other module<br>let localizedStringResource = LocalizedStringResource(<br>    &quot;login.password_entry_password.label&quot;,<br>    defaultValue: &quot;Password&quot;,<br>    comment: &quot;a short label for the password data entry field&quot;,<br>    bundle: .atURL(Bundle.module.bundleURL)<br>)<br>let view = MyView(input: localizedStringResource)</pre><p>This grouping of all important localization information into one type seems to solve all previous issues around modularization, having to context switch to add default language directly in the catalog and comments being ignored.</p><p>It’s also safe like LocalizedStringKey - you can&#39;t accidentally pass in a non-localized String.</p><p>However, it also doesn’t handle already-localized backend strings well and there are some additional concerns to think about:</p><h4>SwiftUI Support</h4><p>LocalizedStringResource is supported in SwiftUI but it&#39;s somewhat limited and for some components, an extra step is required either using Text or String(localized:) as an intermediate:</p><pre>Text(myLocalizedStringResource)<br>Toggle(String(localized: myLocalizedStringResource), isOn: $isOn)<br>SomeView().navigationTitle(Text(myLocalizedStringResource))</pre><h4>Deferred lookup and language choice</h4><p>There’s a broader story for LocalizedStringResource and that&#39;s the need to be able to pass in localized strings into a separate process that may not be running in the same locale context as the main app. App intents are a good example of this and there&#39;s explicit reference to this in the <a href="https://developer.apple.com/documentation/foundation/localizedstringresource">documentation</a>.</p><p>This feels like a somewhat niche usage but it also has an implication when used more broadly that doesn’t appear to be documented by Apple.</p><p>To understand this better, we need to look at how language choice is made by iOS for a specific app. Essentially it compares the app languages available to the preferred languages as shown below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gKT4dEM536rDZ0I7L8UmiA.png" /></figure><p>You can see the result of this in the settings for the app (which can then be overidden by the user). In this case, the app language would be French as this is the first language on the preferred list that is also supported by the app, and this is what is generally used for language look-up at runtime.</p><p>However, LocalizedStringResource appears to operate differently. It appears to use the current locale as a substitute for language. In most simpler cases this does not make a difference - e.g. the user has only French in their preferred language choice. But in the case above the current locale of the user in the app is es_US not French. It will fail that look-up and so default to English.</p><p>Current locale is not the same as preferred language.</p><p>And unless you manually inject Bundle.main.preferredLocalizations into the locale of LocalizedStringResource, it will also ignore any app-level manually overridden language choice. This feels problematic.</p><p>(And note — to test this out use a real device not the simulator.)</p><h4>LocalizedStringResource — Summary</h4><p>✅ Stronger typing than String<br>✅ Handles in-line default values<br>✅ Comment and table grouped with key<br>✅ Modular code<br>❌ Not fully integrated with SwiftUI<br>❌ Awkward for backend strings (require conversion, will still do look-up)<br>❌ Sometimes language look-up doesn’t match the general approach</p><h3>String with String(localized:)</h3><p>Considering the limitations of LocalizedStringKey and LocalizedStringResource we could continue to use String using String(localized:) directly before calling the UI Component. This string initializer is a replacement for the legacy NSLocalizedString.</p><p>Our example would be:</p><pre>struct MyView: View {<br>  let input: LocalizedStringKey<br>  var body: some View {<br>    Text(input)<br>   }<br>}<br><br>// Somewhere else or some other module<br>let string = String(<br>    localized: &quot;login.password_entry_password.label&quot;,<br>    defaultValue: &quot;Password&quot;,<br>    comment: &quot;a short label for the password data entry field&quot;,<br>    bundle: .module<br>)<br>let view = MyView(input: string)</pre><p>This approach removes all the limitations — integrates well with SwiftUI, works cleanly with modularization, comments, in-line default values, and has a consistent language look-up.</p><p>The one downside is — of course — this approach isn’t as perhaps as safe as we want. Strings that have not been localized can be passed in and the compiler will not catch it.</p><h4>String + String(localized:) — Summary</h4><p>❌ Does not ensure localization<br>✅ Integrated with SwiftUI<br>✅ Handles in-line default values<br>✅ Comment and table grouped with key<br>✅ Modular code<br>✅ Backend strings<br>✅ Language look-up</p><h3>Conclusion</h3><p>In this article, I’ve tried to give you reasons for each choice so you can make your own decision. You may well have a simpler app that does not use modules and LocalizedStringKey will work for you. Or maybe you like the power of LocalizedStringResourceand its type-safety and aren&#39;t concerned about its limitations.</p><p>I’m overall concerned, however, with Apple’s recommendation made without full context. Both modularization and the need for consistent language look-up would make it difficult for me to recommend anything other than continuing to use String in the majority of cases. Would be great to have a version of LocalizedStringResource that would do language look-up consistently.</p><p>I have a caveat though. For every app codebase I’ve looked at, there always seems to be a unique approach to localization. Would love to hear your thoughts.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=56cb519cf098" width="1" height="1" alt=""><hr><p><a href="https://levelup.gitconnected.com/ios-localization-localizedstringresource-vs-localizedstringkey-vs-string-56cb519cf098">iOS Localization: LocalizedStringResource vs LocalizedStringKey vs String</a> was originally published in <a href="https://levelup.gitconnected.com">Level Up Coding</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[iOS 17 @Observable and the Observation Framework]]></title>
            <link>https://medium.com/better-programming/ios-17-observable-and-the-observation-framework-152deaf8fc5e?source=rss-c5b0b1aec01a------2</link>
            <guid isPermaLink="false">https://medium.com/p/152deaf8fc5e</guid>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[xcode]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Nick McConnell]]></dc:creator>
            <pubDate>Tue, 13 Jun 2023 04:28:37 GMT</pubDate>
            <atom:updated>2024-02-04T23:56:08.280Z</atom:updated>
            <content:encoded><![CDATA[<h4>The new approach to observing simplifies SwiftUI and solves the nested observable object problem.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZCfVBtoaBJKHaDvaH9hlmA.jpeg" /></figure><p>WWDC 2023 introduced us to the new iOS-17@Observable property wrapper along with a cleaned-up @State that now supersedes the previous @State @ObservedObject, @StateObject. This is fantastic — it’s always been a source of confusion for those starting on SwiftUI and a source of bugs (with various recommendations on what to use and when).</p><p>All this feels like it’s gone and we are left with the simple choice of @Environment, @State, and @Bindable with clean usage (in order: app-wide, view-wide, and binding to a parent). Love it!</p><p>Let’s take a quick look at how this works. You’ll notice that tapping the button triggers a screen refresh to display the new value of myString. You also no longer need @Published. Fantastic!</p><pre>import SwiftUI<br>import Observation<br><br>@Observable<br>class Model {<br>    var myString: String = &quot;Hello&quot;<br>}<br><br>struct ContentView: View {<br>    @State var model: Model<br><br>    var body: some View {<br>        VStack {<br>            Text(&quot;myString: \(model.myString)&quot;)<br>            Button(&quot;Hit me&quot;) {<br>                model.myString = &quot;new&quot;                <br>            }<br>        }<br>    }<br>}</pre><p>Macros appear to be the driver — so is this simply a string replacement of the old approach? Well, by digging deeper and using the new “expand macro” option in Xcode we can start to see what is going on:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/943/1*zkG6yuWqBHFp6SbtQ2iJCw.png" /><figcaption>The @Observable Macro Expanded</figcaption></figure><p>Well, this is different! Digging further we see that Observable is a protocol in the Observation framework — which is brand new. We had to import this framework so perhaps this isn’t a shock. The previous ObservableObject was actually part of Combine and this one looks similar. Additionally, the new @Model also uses this.</p><p>There is a hint in the WWDC video <a href="https://developer.apple.com/wwdc23/10149?time=504">Discover Observation in SwiftUI — WWDC23 — Videos — Apple Developer</a> which suggests something more. In the video, it discusses the observability of arrays which leads me to ponder the existing issue of nested objects in observables which has a <a href="https://holyswift.app/how-to-solve-observable-object-problem/">variety of solutions</a> and perhaps even the suggestion of an anti-pattern.</p><p>Here’s an example of where the previous approach didn’t work. Tapping the button here would not have the intended effect of triggering a view refresh as the Model object itself never changed (only an object being referenced by the Model). The array of references itself remains unchanged and therefore a change event is never triggered.</p><pre>import Combine<br>import SwiftUI<br><br>class Model: ObservableObject {<br>    @Published var str = &quot;Outer&quot;<br>    @Published var innerModels: [InnerModel] = [InnerModel(), InnerModel(), InnerModel()]<br>}<br><br>class InnerModel {<br>    @Published var str = &quot;inner&quot;<br>}<br><br>struct ContentView: View {<br>    @ObservedObject var model: Model<br>    var body: some View {<br>        VStack {<br>            List(model.array, id: \.id) { element in<br>                Text(element.str)<br>            }<br>            Button(&quot;Hit me&quot;) {<br>                model.innerModels[1].str = &quot;KABOOM&quot;<br>            }<br>        }<br>        .padding()<br>    }<br>}</pre><p>There is a solution to the above — switch the inner model to using structs and the problem is solved but value semantics may not be desired.</p><p>The hint in the WWDC that the new approach works for arrays too suggests that this problem has been solved. Let’s take a look:</p><pre>import SwiftUI<br>import Observation<br><br>@Observable class Model {<br>    var innerModels: [InnerModel] = [InnerModel(), InnerModel(), InnerModel()]<br>}<br><br>@Observable class InnerModel: Identifiable {<br>    var str = &quot;inner&quot;<br>}<br><br>struct ContentView: View {<br>    @State var model = Model()<br><br>    var body: some View {<br>        VStack {<br>            List($model.innerModels, id: \.id) { element in<br>                Text(element.str.wrappedValue)<br>            }<br>            Button(&quot;Hit me&quot;) {<br>                model.innerModels[1].str = &quot;KABOOM&quot;<br>            }<br>        }<br>    }<br>}</pre><p>And you’ll discover that it has!! This is great — allows us to think a lot less about code “plumbing” — it just works!! (Caveat — still early days in testing this!)</p><p>So what is the secret sauce?</p><p>The driver behind the Observation framework is the ability to detect a change in the properties contained in a closure of a new function called withObservationTracking.</p><p>For example:</p><pre>func testObservation() {<br>    withObservationTracking {<br>        print(model.inner[1].str)<br>    } onChange: {<br>        print(&quot;Schedule renderer.&quot;)<br>    }<br>}</pre><p>The onChange closure is <em>only</em> called when specifically model.inner[1].str has changed. Changing other properties on the model or other objects in the array or even other properties on model.inner[1]does nothing. That is magical! And could this be useful outside the explicit SwiftUI usage?</p><p>Please refer to the <a href="https://developer.apple.com/documentation/observation">Observation Framework documentation</a> for more details.</p><p>Side note, now that we don’t need @Published — everything automatically updates — perhaps there are cases where you don’t want a property to trigger updates. The new ObservationIgnored can be used for particular properties that should not be observed:</p><pre>@Observable<br>class Model {<br>    @ObservationIgnored var myString: String = &quot;Hello&quot;<br>    var innerModels: [InnerModel] = [InnerModel(), InnerModel(), InnerModel()]<br>}</pre><p>For those of us lucky enough to be able to target iOS 17 in our projects — happy coding! For the rest of us, the specific iOS 17 target means we’ll have to wait to use this magic.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=152deaf8fc5e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/ios-17-observable-and-the-observation-framework-152deaf8fc5e">iOS 17 @Observable and the Observation Framework</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Flow Navigation With SwiftUI 4]]></title>
            <link>https://medium.com/better-programming/flow-navigation-with-swiftui-4-e006882c5efa?source=rss-c5b0b1aec01a------2</link>
            <guid isPermaLink="false">https://medium.com/p/e006882c5efa</guid>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[swiftui]]></category>
            <dc:creator><![CDATA[Nick McConnell]]></dc:creator>
            <pubDate>Wed, 27 Jul 2022 21:23:11 GMT</pubDate>
            <atom:updated>2022-07-31T19:49:53.009Z</atom:updated>
            <content:encoded><![CDATA[<h4>Implementing the new NavigationStack programmatically and without NavigationLink</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*4xIEdjuneEuX2bC6x6koyw.jpeg" /></figure><p>Having just revisited this navigation for SwiftUI 3 <a href="https://betterprogramming.pub/flow-navigation-with-swiftui-revisited-791f89421923">here</a> (which updated the original approach for SwiftUI 1 <a href="https://medium.com/swlh/flow-with-swiftui-and-mvvm-7cc394440ab8">here</a>), Apple has since rethought navigation with the new NavigationStack as part of the latest SwiftUI 4 release. This is great news… and covers most of my previous suggestions!</p><p>Previously, NavigationView required explicitly defining navigation “edges” and the use of multiple flags which could lead to confusion. The new approach uses a stack creating a non-UI representation of the existing navigation and works beautifully with our previous programmatic approach without many changes.</p><p>This approach initially started with a review of a multiscreen onboarding flow with SwiftUI. As with all multiscreen data entry flows, they often represent an interesting problem of how to decouple data, view, and navigation logic.</p><p>So, what makes a great multiscreen data entry flow? Here’s what I came up with. For want of a less grand term, I’ll call it my “screen flow manifesto.” I use the “screen” here rather than view because we are explicitly referring to whole-screen navigation.</p><ol><li>Screens should have no “parent” knowledge nor be responsible for navigating in or out.</li><li>Individual view models for every screen.</li><li>Overall flow control logic is separate from UI implementation and is testable without UI.</li><li>Flexible and allow for branching to different screens in the flow.</li><li>As simple as possible but composable and scalable.</li></ol><h3>Navigation Requirement</h3><p>So onboarding may be simple, perhaps two or three screens asking the user some simple personal information. A “next” button would move the user forward in the flow.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/510/1*xsppo7bUIGvpjDjlmD30JA.png" /><figcaption>Simple Screen Flow</figcaption></figure><p>However, what’s usually more typical is a more complex flow with branching. Maybe the user isn’t ready to share all those details yet or perhaps more details are needed depending on previous responses. So, maybe this is more representative:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/689/1*m0R8rq9raeU_CS27tcE_4Q.png" /><figcaption>Screen Flow with Branching</figcaption></figure><h3>Initial Implementation</h3><p>As mentioned previously, we will be using NavigationStack. This can be bound (2-way binding) to a navigation path. In our first implementation with just a 3-screen flow, we are going to useNavigationPath() which is a type-erased sequence. We will add the navigation path to a navigation-focused view model and pass this around (more later).</p><p>Within NavigationStack we define a root view (in our case a VStack with text and a button). This also contains navigation destination modifiers that trigger the actual navigation. Any appending to the navigation path will point SwiftUI to the appropriate new view for the new screen based on the type and execute a push animation.</p><p>In this implementation, we use a view model called FlowVM that controls the navigation flow (different from screen view models). This view model contains the navigation path allowing us to trigger the actual navigation outside of the views (manifesto points 1 and 3).</p><p>In our example, adding an integer to the navigation path will push aContentView2 to the stack, and adding a string to the path will push a ContentView3. Now simple manipulation of only the navigation path (which is a sequence) will directly affect navigation giving us full programmatic control. Here lies the beauty (and covers manifesto point 4)!</p><ol><li>Push: Append a particular type.</li><li>Back to root: Reinitialize the navigation path.</li><li>Back 1 screen: Remove the last value.</li></ol><p>We can also go back to multiple screens withremoveLast(x).</p><p>You’ll also note we no longer need to useNavigationLink at all. NavigationLink is still an available option in SwiftUI 4 but its main use is in view-to-view navigation — something we want to avoid here!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2b38020f9d1e1f5e67a69c03e6ffb492/href">https://medium.com/media/2b38020f9d1e1f5e67a69c03e6ffb492/href</a></iframe><h3>View Models and Binding</h3><p>On to manifesto point 2 — separate view models for each screen. Usage and implementation of view models may differ here and I’ve heard concerns about the overuse of the MVVM design pattern in SwiftUI. It certainly isn’t a term used in anything official from Apple.</p><p>What I want is a non-UI representation of the view so I can cleanly encapsulate non-UI logic, unit test it without the view, and of course, easily bind to the view (both ways). It also should be specific to the view so the view can be moved around and is not dependent on anything external (i.e., composable — manifesto point 5). It is the interface of the view to the rest of the application. I call this a view model.</p><p>Within SwiftUIObservableObject (which is actually part of Combine) makes for a good view model that enables two-way view binding. The newer approach with @StateObject creates a stable view model which is lazily loaded only when needed.</p><p>Note also that in this version of a view model, UI events are also passed into the view model from the view, and any view-specific logic (e.g., network calls) may be triggered from there (usually calling down to an API layer for example).</p><p>We also have the flow view model (FlowVM) to manage the screen-to-screen navigation. It does not know the views and is designed to be testable. It itself may require API calls to determine the path to follow. Note this is similar to a “coordinator” but to me is considered a model of the navigation and therefore I’ve used the term “view model.”</p><p>Each screen then also has individual view models as well. These screen view models handle the UI events and screen logic. Ultimately (upon completion of all screen logic after a “next” tap for example), we pass the control back from the screen view models to the flow view model to ultimately decide on where to navigate.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I87_gYdffKaiNDEaXSc3SQ.png" /></figure><p>For completion, eventing back from the screen view models back “up” to the flow view model, we can use a variety of techniques. Delegates and call-backs are all valid implementations but I like to use Combine’s PassthroughSubject passing back a reference to the screen view model itself.</p><p>So the screen view model and the view would like something like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9ced4fa5247121088b86a4e822551d01/href">https://medium.com/media/9ced4fa5247121088b86a4e822551d01/href</a></iframe><p>And wired in the flow view model to listen to the completion events as follows using a sink and storing that in a subscription. You’ll notice the factory function to create the screen view model is handled here which also added the event listening. This factory function is called by the flow view in screen view initialization.</p><p>The sink calls a method directly to handle any logic (and navigation) and stores the subscription in a set attached to the view model (which can be used for all subscriptions).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1ee32df6e12d3b97c6c3452ba8ccf3d3/href">https://medium.com/media/1ee32df6e12d3b97c6c3452ba8ccf3d3/href</a></iframe><h3>Bringing It Together</h3><p>In our first implementation of the navigation stack, we used NavigationPath and rather non-sensical types (integers and strings) to drive the navigation. As each screen is now represented with a view model we can actually drive the navigation by adding the view models themselves to the path.</p><p>We could add the view models directly to NavigationPath and create multiple navigation destination modifiers for each view model type. However, this type erased sequence offers only limited inquiry capability (for example — cannot easily inquire on what screen is currently shown).</p><p>Instead, we can bind the navigation stack to a simple enum array that contains an associated value of the view model. Now the path is an array we have maximum control and introspection of its current state. The only requirement here is that the array is Hashable, which in turn requires the view models to be Hashable. A little extra work here, but straightforward.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5b13d6fb8c762a76cd13568b208f96a7/href">https://medium.com/media/5b13d6fb8c762a76cd13568b208f96a7/href</a></iframe><p>Check out the <a href="https://github.com/nickm01/NavigationFlow">repo</a> for full code. This also includes examples of backward navigation (including back to root or screen two, etc) and Hashable conformance.</p><p>The “maximum control” also allows us to handle some interesting situations that I don’t recall being able to do in UIKit. You can change a previous screen to be something else entirely (navigationPath[0] = ... ) and now the back button goes to a different screen. Or weirdly removing a screen previous screen deeper in the stack (e.g. navigationPath.removeFirst() ). This will programmatically navigate backward with the first screen removed from the stack. Perhaps the last one is non-sensical but I like the way SwiftUI worked as I expected even in these odd situations. Well done Apple!</p><h3>Testing</h3><p>A big part of our design is to improve testability and allow for unit tests of the navigation flow independent of the UI (manifesto point 3). Now with view models, this is easily done. Here’s an example:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d142e382d6f30d99be5910bd9dd70ab2/href">https://medium.com/media/d142e382d6f30d99be5910bd9dd70ab2/href</a></iframe><p>We are able to trigger a “next” button tap and then check the navigation logic has been triggered — all without actual UI.</p><p>Note though this is obviously a simple implementation. If the view models had API calls, we would have to think about some injection to mock those out. In addition, this is obviously not a UI test.</p><p>We may also want to add some UI tests (perhaps using snapshot testing) — but this is beyond the scope of this article.</p><h3>And Finally…</h3><p>I hope this has made sense! After 4 versions, this iteration of SwiftUI’s push navigation is the approach we’ve been looking for. This should now have answered most of the concerns of the community at large (as well as my previous suggestions <a href="https://betterprogramming.pub/flow-navigation-with-swiftui-revisited-791f89421923">here</a>).</p><p>Any improvements? Right now, there is no obvious path to creating custom push navigation. An additional larger thought is to create a single, converged navigation API for both push and modal navigation. Apple — your clock starts now! 😁</p><p>The full code can be found at <a href="https://github.com/nickm01/NavigationFlow">https://github.com/nickm01/NavigationFlow</a>. Enjoy!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e006882c5efa" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/flow-navigation-with-swiftui-4-e006882c5efa">Flow Navigation With SwiftUI 4</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Flow Navigation With SwiftUI (Revisited)]]></title>
            <link>https://medium.com/better-programming/flow-navigation-with-swiftui-revisited-791f89421923?source=rss-c5b0b1aec01a------2</link>
            <guid isPermaLink="false">https://medium.com/p/791f89421923</guid>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Nick McConnell]]></dc:creator>
            <pubDate>Wed, 27 Apr 2022 18:51:49 GMT</pubDate>
            <atom:updated>2022-07-31T00:46:29.585Z</atom:updated>
            <content:encoded><![CDATA[<h3>Flow Navigation With SwiftUI 3</h3><h4>How to implement screen flow navigation effectively in your code bases</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*4xIEdjuneEuX2bC6x6koyw.jpeg" /></figure><p>[<em>Note for implementation with SwiftUI 4 and NavigationStack see </em><a href="https://betterprogramming.pub/flow-navigation-with-swiftui-4-e006882c5efa"><em>here</em></a><em>]</em></p><p>This is a revisit of a previous couple of articles on creating a decoupled navigation flow (<a href="https://medium.com/swlh/flow-with-swiftui-and-mvvm-7cc394440ab8">part 1</a> and <a href="https://medium.com/@nicmcconn/flow-with-swiftui-and-mvvm-part-2-viewmodels-905ecc05f1c5">part 2</a>) which related to the original SwiftUI 1.0. Times have changed and SwiftUI (3.0), NavigationView, and my own perspective are now different (and simpler!) so thought it was worthwhile re-evaluating.</p><p>I’ve recently been reviewing my multiscreen onboarding flow with SwiftUI. As with all multiscreen data entry flows, they often represent an interesting problem of how to decouple data, view, and navigation logic.</p><p>So, what makes a great multiscreen data entry flow? Here’s what I came up with. For want of a less grand term, I’ll call it my “screen flow manifesto.” I use the “screen” here rather than view because we are explicitly referring to whole-screen navigation.</p><ol><li>Screens should have no “parent” knowledge nor be responsible for navigating in or out.</li><li>Individual view models for every screen.</li><li>Overall flow control logic is separate from UI implementation and is testable without UI.</li><li>Flexible and allow for branching to different screens in the flow.</li><li>As simple as possible but composable and scalable.</li></ol><h3>Navigation</h3><p>So onboarding may be simple, perhaps two or three screens asking the user some simple personal information. A “next” button would move the user forward in the flow.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/510/1*xsppo7bUIGvpjDjlmD30JA.png" /><figcaption>Simple Screen Flow</figcaption></figure><p>However, what&#39;s usually more typical is a more complex flow with branching. Maybe the user isn’t ready to share all those details yet or perhaps more details are needed depending on previous responses. So, maybe this is more representative:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/689/1*m0R8rq9raeU_CS27tcE_4Q.png" /><figcaption>Screen Flow with Branching</figcaption></figure><p>Obviously, any solution would need to handle any combination of the above and, as per manifesto point 1 to do so outside of the screens themselves. It should also be noted that we probably want to do some data look up at the end of each screen’s entry as we don’t want the view itself to control navigation (manifesto point 3).</p><p>As we are in the world of SwiftUI, I propose using the power of @ViewBuilder. This is the “meat” inside the SwiftUI view’s body. ViewBuilders are a powerful way of generating complex generic types — which is what is behind the declarative nature of SwiftUI (but is beyond the scope of this article).</p><p>So what could that look like? Well, a good start is SwiftUI’s equivalent of UINavigationController which is NavigationView. In this, we add a ViewBuilder equivalent of a tree structure to represent the navigation nodes and edges:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c6031d164678f06311d1978dfe42fe12/href">https://medium.com/media/c6031d164678f06311d1978dfe42fe12/href</a></iframe><p>OK, so this really is pseudocode. Full disclosure — it ain’t that simple 😀.</p><p>This is still “declarative,” in the sense that it’s predefined rather than completely programmatic but dynamic where the paths are driven by programming logic. You still require each navigation “edge” to be defined up-front. If your flows are completely dynamic with no particular set paths, then perhaps this isn’t the approach for you.</p><p>With that said, let’s try and get a close implementation of this approach. Using the embedded types we can create a good declarative definition of the branching flow diagram from above. It satisfies manifesto point 1 and perhaps point 5. So, let’s see if we can implement something like this.</p><p>NavigationView pairs with NavigationLink to give us the ability to do “traditional” push navigation. There are a few variations of use, but I honed in on the fully-programmatic variation:</p><pre>NavigationLink(destination: Destination, isActive: Binding&lt;Bool&gt;) { Label }</pre><p>After some experimentation here (and frustration with the lack of documentation), here is a list of considerations for using NavigationLink:</p><ol><li>Needs to be embedded in a grouping such as a VStack.</li><li>The Label is typically Text if we want a simple active link to control navigation. However, in our case, we do not want the view of external programmatic control of navigation so we use EmptyView.</li><li>You can easily go wrong with the binding. If you want external control of navigation (and we do), using the newer@StateObject (rather than @ObservedObject — see below) with flags for each navigation works nicely.</li><li>I disliked the order. To me, the trigger for navigation reads better if it’s before the destination.</li></ol><p>This resulted in an improved encapsulation of NavigationLink:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fd33f5e74b8c00a8d1894fc3f5c58a49/href">https://medium.com/media/fd33f5e74b8c00a8d1894fc3f5c58a49/href</a></iframe><p>This encapsulates some of the complexity of NavigationLink usage. We can pass in a bound flag to allow us to externally control navigation. The plumbing work of needing to use VStack and EmptyView is done for us. It also makes use of @ViewBuilder to make a variation of NavigationLink that reads better.</p><p>Let’s see it in action for a simple three-screen flow. We’ve introduced an observable object to encapsulate the navigation flags (more to follow). Here’s the code:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7abada6236b7cbe725983152c39c216f/href">https://medium.com/media/7abada6236b7cbe725983152c39c216f/href</a></iframe><p>Screens 1 and 2 both contain three types: AText to display the screen name, the Button for the next action, and a Flow for the navigation. We store the flow state for each navigation and internal functions perform the actual navigation (didTapNext1 etc).</p><p>This works but perhaps is overkill if the next buttons themselves directly do the navigation. Other forms of NavigationLink can fill that role just as well perhaps. However, as part of manifesto part 3, we want our navigation to be controlled externally from views.</p><p>It should be noted that navigation should be activated based on the currently active screen — setting navigateTo3 to true when the user is on screen 3 will cause some “odd” results (it does navigate but instantly, without animation).</p><p>Backward navigation to a previous screen (including root) can be achieved by setting the flag that originally moved the user away from this destination screen to false. In the case above for programmatically going back to screen 1 from screen 2, by setting navigateTo2 = false.</p><p>Note: A new feature in iOS 15 allows you to use @Environment(\.dismiss). However, this simply goes back one screen and does not allow for a larger backward jump (or pop to root). Using the activation flags allows for more complete control.</p><h3>View Models and Binding</h3><p>On to manifesto point 2 — separate view models for each screen. Usage and implementation of view models may differ here and I’ve heard concerns about the overuse of the MVVM design pattern in SwiftUI. It certainly isn’t a term used in anything official from Apple.</p><p>What I want is a non-UI representation of the view so I can cleanly encapsulate non-UI logic, unit test it without the view, and of course, easily bind to the view (both ways). It also should be specific to the View so the View can be moved around and is not dependent on anything external (i.e., composable — manifesto point 5). It is the interface of the view to the rest of the application. I call this a view model.</p><p>Within SwiftUIObservableObject (which is actually part of Combine) makes for a good view model that enables two-way view binding. Using @ObservedObject in the View itself, however, can be problematic and cause unnecessary view model recreation. The newer approach with @StateObject creates a stable view model which is lazily loaded only when needed. This is a large and important improvement on the old approach where a workaround was used.</p><p>Note also that in this version of a view model, UI events are also passed into the view model from the view, and any view-specific logic (e.g., network calls) may be triggered from there (usually calling down to an API layer for example).</p><p>To model this out, we have a flow view model (FlowVM) to manage the screen-to-screen navigation. It does not know the views and is designed to be testable. It itself may require API calls to determine the path to follow. This is similar to a “coordinator” but to me is considered a model of the navigation and therefore I’ve used the term “view model.”</p><p>Each screen then also has individual view models as well. These screen view models handle the UI events and screen logic. Ultimately (upon completion of all screen logic after a “next” tap for example), we pass the control back from the screen view models to the flow view model to ultimately decide on where to navigate.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I87_gYdffKaiNDEaXSc3SQ.png" /></figure><p>For completion, eventing back from the screen view models back “up” to the flow view model, we can use a variety of techniques. Delegates and call-backs are all valid implementations but I like to use Combine’s PassthroughSubject passing back a reference to the screen view model itself.</p><p>So the screen view model and the view would like something like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9ced4fa5247121088b86a4e822551d01/href">https://medium.com/media/9ced4fa5247121088b86a4e822551d01/href</a></iframe><p>And wired in the flow view model to listen to the completion events as follows using a sink and storing that in a subscription. You’ll notice the factory function to create the screen view model is handled here which also added the event listening. This factory function is called by the flow view in screen view initialization.</p><p>The sink calls a method directly to handle any logic (which, in this case, is just simply setting the navigate flag) and stores the subscription in aSet attached to the vm (which can be used for all subscriptions).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/525fea54448ed16cc9945b443438cb66/href">https://medium.com/media/525fea54448ed16cc9945b443438cb66/href</a></iframe><h3>Bringing It Together</h3><p>Let’s see how that looks in totality, after adding in our five-screen branched flow. You’ll notice there is a separate navigation flag for each navigation edge. You will also note that the screen 3 view model would require two didTap() functions and two PassthroughSubjects to handle the branched logic.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6b245230f8a2cf7224ba0b1b43788cdc/href">https://medium.com/media/6b245230f8a2cf7224ba0b1b43788cdc/href</a></iframe><p>Check out the <a href="https://github.com/nickm01/NavigationFlow">repo</a> for full code. This also includes examples of backward navigation (including back to root or screen two, etc).</p><h3>Testing</h3><p>A big part of our design is to improve testability and allow for unit tests of the navigation flow independent of the UI (manifesto point 3). Now with view models, this is easily done. Here’s an example:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/69ca405daa1b132a0a3a9b5610d03154/href">https://medium.com/media/69ca405daa1b132a0a3a9b5610d03154/href</a></iframe><p>We are able to trigger a “next” button tap and then check the navigation logic has been triggered — all without actual UI.</p><p>Note though this is obviously a simple implementation. If the view models had API calls, we would have to think about some injection to mock those out. In addition, this is obviously not a UI test.</p><p>We may also want to add some UI tests (perhaps using snapshot testing) — but this is beyond the scope of this article.</p><h3>And Finally…</h3><p>I hope this has made sense! It’s definitely been a journey trying to make sense of how to use navigation, and I’m happy to share. The navigation has improved recently, and I do hope Apple continues to improve it. Here are some suggestions:</p><ol><li>There are too many ways to make mistakes with navigation when it’s beyond the simple case — and too little documentation. For example, using @ObservedObject, using the wrong activation flag at the wrong time, etc. I see this in the number of posts and custom libraries out there.</li><li>Allow navigation in a way that not every navigation edge has to be specified and handled from the correct screen, but rather just a way of declaring the navigation to a screen from any screen.</li><li>Create a simpler API for backward navigation (pop to root, etc).</li><li>Rather than UIKit being more flexible (e.g., custom push navigation animations, etc.), make SwiftUI navigation at least as flexible… and, perhaps, even better.</li></ol><p>The full code can be found at <a href="https://github.com/nickm01/NavigationFlow/tree/swiftui3">https://github.com/nickm01/NavigationFlow/tree/swiftui3</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=791f89421923" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/flow-navigation-with-swiftui-revisited-791f89421923">Flow Navigation With SwiftUI (Revisited)</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Flow with SwiftUI and MVVM — Part 2: ViewModels]]></title>
            <link>https://medium.com/swlh/flow-with-swiftui-and-mvvm-part-2-viewmodels-905ecc05f1c5?source=rss-c5b0b1aec01a------2</link>
            <guid isPermaLink="false">https://medium.com/p/905ecc05f1c5</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[mvvm]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <dc:creator><![CDATA[Nick McConnell]]></dc:creator>
            <pubDate>Mon, 22 Jun 2020 05:27:24 GMT</pubDate>
            <atom:updated>2022-04-27T20:28:49.242Z</atom:updated>
            <content:encoded><![CDATA[<h3>Flow with SwiftUI and MVVM — Part 2: ViewModels [deprecated]</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*4xIEdjuneEuX2bC6x6koyw.jpeg" /></figure><p><em>[Note this was been reworked for SwiftUI release 3 — see </em><a href="https://medium.com/@nicmcconn/791f89421923"><em>update</em></a><em>]</em></p><p>In <a href="https://medium.com/swlh/flow-with-swiftui-and-mvvm-7cc394440ab8">part 1</a>, we looked at the navigation structure needed to support a multi-screen data entry flow with branching which typical to but not limited to a user onboarding flow. We partially succeeded in satisfying our <strong><em>“screen flow manifesto</em></strong><em>” </em>and in this final part we look to completing that with introduction of ViewModels.</p><p>As part of point 2 of the <em>manifesto</em> our goal is to have isolated ViewModels for each screen in the flow. SwiftUI works very cleanly with ViewModels giving us 2-way binding with OberservableObject. Overall though, how do we want to structure the generation and usage of ViewModels? There are many ways to implement the MVVM design pattern, but for this case I like to think in these terms:</p><ol><li>ViewModel acts as a simple non-UI data interface to the View. Generally contains only limited business logic — e.g. simple validation etc.</li><li>We have a single model (“the Model”) as our overall data store which is persistent across screens.</li><li>ViewModels can be generated from the Model and can also update the Model.</li><li>Because this is data entry which means updating data, ViewModels and the Model are more naturally as classes rather than structs.</li><li>We will use our FlowController to orchestrate where necessary.</li></ol><p>So visually this could look like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/984/1*bbXWWf6Z5FXh-IY0EXfDxQ.png" /><figcaption>Navigation and Model Structure</figcaption></figure><p>So a screen with it’s paired ViewModel would generically look like this:</p><pre><strong>class</strong> Screen1VM: ObservableObject {<br>    @Published <strong>var</strong> detail1 = &quot;&quot;<br>    @Published <strong>var</strong> detail2 = &quot;&quot;<br>}</pre><pre><strong>struct</strong> Screen1: View {<br>    @ObservedObject <strong>var</strong> vm: Screen1VM<br>    <strong>let</strong> didTapNext: () -&gt; ()</pre><pre><strong>    var</strong> body: <strong>some</strong> View {<br>        VStack(alignment: .center) {<br>            Text(&quot;Please enter details&quot;)<br>            TextField(&quot;Detail1&quot;, text: $vm.detail1)<br>                .textFieldStyle(RoundedBorderTextFieldStyle())<br>            TextField(&quot;Detail2&quot;, text: $vm.detail2)<br>                .textFieldStyle(RoundedBorderTextFieldStyle())                 <br>            Button(<br>                action: { <strong>self</strong>.didTapNext() },<br>                label: { Text(&quot;Next&quot;) }<br>            )<br>        }<br>    }<br>}</pre><p>All well and good but how to we generate the ViewModel and pass the ViewModel back through the Controller to potentially update the main Model? Both actions can be handled by the FlowControllerView delegating to the FlowController.</p><p>Firstly, we update the can pass the ViewMode through the didTapNext function so the above becomes:</p><pre><strong>struct</strong> Screen1: View {<br>    @ObservedObject <strong>var</strong> vm: Screen1VM<br>    <strong>let</strong> didTapNext: (<strong>Screen1VM</strong>) -&gt; ()</pre><pre><strong>    var</strong> body: <strong>some</strong> View {<br>            ...<br>            Button(<br>                action: { <strong>self.</strong>didTapNext(<strong>self.vm</strong>) },<br>                label: { Text(&quot;Next&quot;) }<br>            )<br>            ...</pre><p>FlowControllerView and it’s delegate now need to accept a screen-specific didTapNext and a new function to generate the ViewModel.</p><pre><strong>protocol</strong> FlowControllerViewDelegate: class {<br>    ...<br>    <strong>func</strong> didTapNext(vm: Screen1VM)<br>    ...<br>}</pre><pre><strong>struct</strong> FlowControllerView: View {<br>    ...<br>    <strong>var</strong> body: <strong>some</strong> View {<br>        NavigationView {<br>            VStack() {<br>                Screen1Phone(<br>                    vm: <strong>self</strong>.delegate.make(),<br>                    didTapNext: <strong>self</strong>.delegate.didTapNext<br>                )<br>                Flow(state: navigateTo2) {<br>                    ...<br>                }<br>           ...</pre><p>The 2 new delegate functions are implemented by FlowController. These ultimately require the overall, persistent Model which is owned by the FlowController. This would look like:</p><pre><strong>class</strong> Model {<br>    <strong>var</strong> detail1: String?<br>    <strong>var</strong> detail2: String?<br>    <strong>var</strong> detail3: String?<br>    ...<br><br>    <strong>func</strong> make() -&gt; Screen1VM {<br>        <strong>return</strong> Screen1PhoneVM()<br>    }</pre><pre>    ...</pre><pre><strong>    func</strong> update(with vm: Screen1PhoneVM) {<br>        detail1 = vm.detail1<br>        detail2 = vm.detail2<br>    }</pre><pre>    ...<br>}</pre><pre><strong>class</strong> FlowController {<br>    <strong>...</strong></pre><pre><strong>    let</strong> model: Model</pre><pre>    ...</pre><pre><strong>    init</strong>() {<br>        <strong>self</strong>.model = Model()<br>        ...<br>    }</pre><pre><strong>    func</strong> didTapNext(vm: Screen1VM) {<br>        <em>// Network call to send verification number, then...</em>           <br>        model.update(with: vm)<br><em>        </em>view?.navigate(to: .screen2)<br>    }</pre><pre>    ...</pre><pre><strong>    func</strong> make() -&gt; Screen1VM {<br>        <strong>return</strong> model.make()<br>    }</pre><pre>    ...</pre><pre>}</pre><p>FlowController now simply orchestrates the initialization of the ViewModel, any once the user has entered the screen details, any necessary network calls, the updating of the Model and moving the navigation foward. However, it is not directly responsibility for any of these actions. The Model generates and updates itself from the ViewModel.</p><p>This may seem like overkill for this simple example, but in the real world organizing and scaling this complexity can challenging and this helps to keep responsibilities clear (point 5 in our <em>manifesto</em>). Note also the ViewModel of a screen often needs defaults in its generation, which can be easily handled by the make() function of the Model.</p><p>Validation is beyond the scope of this article, but typically there would be different levels required. Simple validation (such as all fields must be completed) could be handled directly by the logic in the ViewModel and disabled(Bool) added to the screen view’s Button referencing the ViewModel. More complex validation (such as backend calls) would be orchestrated by the FlowController.</p><p>Another important and interesting note is that normally in SwiftUI <em>all</em> views within the hierarchy are instantiated upfront. For us that correlates to all screens <em>and</em> their corresponding ViewModels are initalized at the beginning. If we have some screen’s ViewModels relying on previous screen’s ViewModels this will cause us problems. Luckily there is a fix with a LazyView discussed in detail <a href="https://www.objc.io/blog/2019/07/02/lazy-loading/">here</a> (thank you!), but I do hope that lazy instantiation is more formally handled in the next version of SwiftUI especially in the case of screen navigation.</p><p>So going back to our overall screen flow, lets make it a bit more specific. Something like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HrZhbFdL_957dbtFD5tIDQ.png" /><figcaption>An example onboarding flow</figcaption></figure><p>In screen 1, the user enters a phone number, enters the SMS verification number in screen 2, then personal details in 3 and can either skip to the end or decide to add work email on screen 4. Some screens require data from previous screens. The implementation can be found here <a href="https://github.com/nickm01/NavigationFlow/tree/part2">https://github.com/nickm01/NavigationFlow/tree/part2</a> (this is the part2 branch on the same repo as part 1).</p><p>With the help of lazy computed screens, you’ll notice that the final FlowControllerView implementation is pretty close to our original part 1 pseudo-code:</p><pre><strong>var</strong> body: <strong>some</strong> View {<br>    NavigationView {<br>        VStack() {<br>            screen1Phone<br>            Flow(state: navigateTo2) {<br>                screen2Verification<br>                Flow(state: navigateTo3) {<br>                    screen3NameEmail<br>                    Flow(state: navigateTo4) {<br>                        screen4CompanyInfo<br>                        Flow(state: navigateToFinalFrom4) {<br>                            screen5Final<br>                        }<br>                    }<br>                    Flow(state: navigateToFinalFrom3) {<br>                        screen5Final<br>                    }<br>                }<br>            }<br>        }<br>    }<br>}</pre><p>And lastly a note on unit testing as per point 3 of the <em>manifesto</em>. With the introduction of an injectable view protocol, FlowController can now be unit tested and as it itself has no reference to SwiftUI is independant of actual UI implementation. Navigation, ViewModels and Model orchestration can be independantly tested.</p><p>Hope you enjoyed this journey. Would love to hear any feedback.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=905ecc05f1c5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swlh/flow-with-swiftui-and-mvvm-part-2-viewmodels-905ecc05f1c5">Flow with SwiftUI and MVVM — Part 2: ViewModels</a> was originally published in <a href="https://medium.com/swlh">The Startup</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Flow with SwiftUI and MVVM]]></title>
            <link>https://medium.com/swlh/flow-with-swiftui-and-mvvm-7cc394440ab8?source=rss-c5b0b1aec01a------2</link>
            <guid isPermaLink="false">https://medium.com/p/7cc394440ab8</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[mvvm]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[navigation]]></category>
            <dc:creator><![CDATA[Nick McConnell]]></dc:creator>
            <pubDate>Mon, 08 Jun 2020 05:09:04 GMT</pubDate>
            <atom:updated>2022-07-31T19:26:47.486Z</atom:updated>
            <content:encoded><![CDATA[<h3>Flow with SwiftUI 1 and MVVM — Part 1: Navigation [deprecated]</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*4xIEdjuneEuX2bC6x6koyw.jpeg" /></figure><p><em>[Note this article relates to SwiftUI 1.0. It has been reworked for </em><a href="https://betterprogramming.pub/flow-navigation-with-swiftui-revisited-791f89421923"><em>SwiftUI 3</em></a><em> and separately for </em><a href="https://medium.com/swlh/flow-with-swiftui-and-mvvm-7cc394440ab8"><em>SwiftUI 4</em></a><em>.]</em></p><p>I’ve recently been looking at the creation of a multi-screen onboarding flow for my next app and challenging myself to use SwiftUI completely. As with all multi-screen data entry flows, they often represent an interesting problem of how to separate out data, view and navigation logic. I thought SwiftUI’s declarative nature and lean towards ViewModels would be a great opportunity but navigation does have it’s challenges in SwiftUI as we’ll see.</p><p>Before we start let’s ask the question: What make’s a great multi-screen data entry flow? Here’s what I came up with. For want of a less grand term, I’ll call it my<em> </em><strong><em>“screen flow manifesto</em>”</strong>:</p><ol><li><strong>Screens should have no “parent” knowledge nor be responsible for navigating in or out.</strong></li><li><strong>Individual ViewModels for every screen.</strong></li><li><strong>Overall flow control logic is separate to UI implementation and is testable without UI.</strong></li><li><strong>Flexible and allow for branching to different screens in the flow.</strong></li><li><strong>As simple as possible but scalable.</strong></li></ol><p>SwiftUI’s ObservableObject and @ObservedObject pair seem to work well for ViewModels giving us the 2-way binding that has previously been missing in UIKit. There are a lot of approaches to ViewModels but I like to think of them as a pure data interface to the view which avoids any direct view access.</p><p>But<strong> ViewModel implementation will be discussed in part 2</strong>. In part 1 we will looking at setting up the main navigation flow.</p><h3>Part 1 — Navigation</h3><p>So an on-boarding may be simple, perhaps 2 or 3 screens asking the user some simple personal information. A “next” button would move the user forward in the flow.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/510/1*xsppo7bUIGvpjDjlmD30JA.png" /><figcaption>Simple Screen Flow</figcaption></figure><p>However, whats usually more typical is a more complex flow with <em>branching</em>. Maybe the user isn’t ready to share all those details yet or perhaps the more details on needed depending on previous responses. So maybe this is more representative:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/689/1*m0R8rq9raeU_CS27tcE_4Q.png" /><figcaption>Screen Flow with Branching</figcaption></figure><p>Obviously any solution would need to be handle any combination of the above and, as per<em> manifesto point 1</em> to do so outside of the the screens themselves. It should also be noted that we probably want to do some data look up at the end of each screen’s entry so we don’t want the view itself to control navigation (<em>manifesto point 3</em>)</p><p>As we are in the world of SwiftUI, I propose using the power of @ViewBuilder. This is the “meat” inside the SwiftUI view’s body . ViewBuilders are a powerful way of generating complex generic types — which is what is behind the declarative nature of SwiftUI (but is beyond the scope of this article).</p><p>So what could that look like? Well a good start is SwiftUI’s equivalent of UINavigationController which is NavigationView. Into this we add a ViewBuilder equivalent of a tree structure to represent the navigation nodes and edges:</p><pre><strong>var</strong> body: <strong>some</strong> View {<br>    NavigationView {<br>        <strong>Screen1</strong>()<br>        Flow {<br>            <strong>Screen2</strong>()<br>            Flow {<br>                <strong>Screen3</strong>()<br>                Flow {<br>                    <strong>FinalScreen</strong>()<br>                }<br>                Flow {<br>                    <strong>Screen4</strong>()<br>                    Flow {<br>                        <strong>FinalScreen</strong>()<br>                    }<br>                }<br>            }<br>        }<br>    }<br>}</pre><p>OK, so this really is pseudo code. Full disclosure — it ain’t that simple 😀.</p><p>But let’s try and get close. Using the imbedded types we can create a good declarative definition of the branching flow diagram from above. It satisfies <em>manifesto point 1</em>, <em>point 2</em>, and perhaps <em>point 5</em>. So let’s see if we can implement something like this.</p><p>NavigationView pairs with NavigationLink to give us the ability to do “traditional” push navigation. There are a few variations of use, but I honed into the fully-programmatic variation:</p><pre>NavigationLink(destination: Destination, isActive: Binding&lt;Bool&gt;) { Label }</pre><p>After some experimentation here (and frustration with lack of documentation), here are a list considerations of using NavigationLink:</p><ol><li>Needs to be embedded in a grouping such as a VStack.</li><li>The Label is typically Text if we want a simple active link to control navigation. However, in our case do not want the view in direct control of navigation so we use EmptyView.</li><li>You can easily go wrong with the binding. If you want external control of navigation (and we do), using @State does not work as @State is often disconnected to its backing store when used externally.ObservableObject looks promising but after experimentation best controlled by use of one ObservableObject per NavigtaionLink. I had initially tried to use one ObservableObject for all links but this failed miserably.</li><li>I disliked the order. To me the trigger for navigation reads better if it’s before the destination.</li></ol><p>The resulted an improved encapsulation of NavigationLink:</p><pre><strong>class</strong> FlowState: ObservableObject {<br>    @Published <strong>var</strong> next: Bool = <strong>false<br></strong>}</pre><pre><strong>struct</strong> Flow&lt;Content&gt;: View <strong>where</strong> Content: View {<br>    @ObservedObject <strong>var</strong> state: FlowState<br>    <strong>var</strong> content: Content<br>    <strong>var</strong> body: <strong>some</strong> View {<br>        NavigationLink(<br>            destination: VStack() { content },<br>            isActive: $state.next<br>        ) {<br>            EmptyView()<br>        }<br>    }</pre><pre><strong>    init</strong>(state: FlowState, @ViewBuilder content: () -&gt; Content) {<br>        <strong>self</strong>.state = state<br>        <strong>self</strong>.content = content()<br>    }<br>}</pre><p>This encapsulates some of the complexity of NavigationLink usage. We can pass in a FlowState to allow us to externally control navigation. The plumbing work of needing to use VStack and EmptyView is done for us. It also makes use of @ViewBuilder to make a variation of NavigationLink that reads better.</p><p>Let’s see it in action in for a simple 2 screen flow.</p><pre><strong>private</strong> <strong>let</strong> navigateTo2 = FlowState()<br><strong>private</strong> <strong>let</strong> navigateTo3 = FlowState()</pre><pre><strong>var</strong> body: <strong>some</strong> View { <br>    NavigationView {<br>        VStack() {<br>            Text(&quot;<strong>Screen 1</strong>&quot;)<br>            Button(<br>                action: { <strong>self</strong>.navigateTo2.next = <strong>true</strong> },<br>                label: { Text(&quot;Next&quot;) }<br>            )<br>            Flow(state: navigateTo2) {<br>                Text(&quot;<strong>Screen 2</strong>&quot;)<br>                Button(<br>                    action: { <strong>self</strong>.navigateTo3.next = <strong>true</strong> },<br>                    label: { Text(&quot;Next&quot;) }<br>                )<br>                Flow(state: navigateTo3) {<br>                    Text(&quot;<strong>Screen 3</strong>&quot;)<br>                }<br>            }<br>        }<br>    }<br>}</pre><p>Screens 1 and 2 both contain 3 types: AText to display screen name, the Button for the next action and a Flow for the navigation. We store the flow state for each navigation and internal functions perform the actual navigation (didTapNext1 etc).</p><p>This works but perhaps is overkill if the next buttons themselves directly do the navigation. Other forms of NavigationLink can fill that role just as well perhaps. However, as part of <em>manifesto part 3 </em>we want our navigation to be controlled externally from views. Typically a “next” button press in an on-boarding flow will require a backend call to validate or save data in each step. To model this out, we could think in terms of a FlowController being in control of the navigation flow which has no knowledge of the views themselves.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/886/1*CetY_1lAHgzfznu3DLpqEg.png" /><figcaption>Externalizing navigation using a FlowController</figcaption></figure><p>It makes sense to have the FlowController own the FlowView and use delegation to event “next requests” back up. We can then wire up the next button taps from the screen views to the delegate functions. So a typical screen view may look something like this:</p><pre><strong>struct</strong> Screen: View {<br>    <strong>let</strong> title: String<br>    <strong>let</strong> didTapNext: () -&gt; ()</pre><pre><strong>    var</strong> body: <strong>some</strong> View {<br>        VStack() {<br>            Text(title)<br>            Button(<br>                action: { <strong>self</strong>.didTapNext() },<br>                label: { Text(&quot;Next&quot;) }<br>            )<br>        }<br>    }<br>}</pre><p>And implemented something like this:</p><pre>Screen(<br>    title: &quot;<strong>Screen 1</strong>&quot;, <br>    didTapNext: { <strong>self</strong>.modelDelegate.didTapNext(request: .screen2) }<br>)</pre><p>Let’s see how that looks in totality with the additionally adding in our <strong>5 screen branched flow.</strong> You’ll notice there is a separate NavigateTo case paired with a flow state observable for each every navigation <em>edge. </em>(For readability the Screen instantiation has been shortened — please see the <a href="https://github.com/nickm01/NavigationFlow">repo</a> for full code).</p><pre><strong>protocol</strong> FlowControllerViewDelegate: class {<br>    <strong>func</strong> didTapNext(request: NavigateTo)<br>}</pre><pre><strong>class</strong> FlowController {<br>    <strong>var</strong> view: FlowControllerView?<br>    <strong>init</strong>() {<br>        <strong>self</strong>.view = FlowControllerView(delegate: <strong>self</strong>)<br>    }<br>}</pre><pre><strong>extension</strong> FlowController: FlowControllerViewDelegate {<br>    <strong>func</strong> didTapNext(request: NavigateTo) {<br>        <em>// In the real world, would do a switch here on NavigateTo,<br>        // followed by potentially some network calls<br>        // before finally...<br>        </em>view?.navigate(to: request)<br>    }<br>}</pre><pre><strong>enum</strong> NavigateTo {<br>    <strong>case</strong> screen1<br>    <strong>case</strong> screen2<br>    <strong>case</strong> screen3<br>    <strong>case</strong> screen4<br>    <strong>case</strong> finalFrom3<br>    <strong>case</strong> finalFrom4<br>}</pre><pre><strong>struct</strong> FlowControllerView: View {<br>    <strong>weak</strong> <strong>var</strong> delegate: FlowControllerViewDelegate!<br>    <strong>private</strong> <strong>let</strong> navigateTo2 = FlowState()<br>    <strong>private</strong> <strong>let</strong> navigateTo3 = FlowState()<br>    <strong>private</strong> <strong>let</strong> navigateTo4 = FlowState()<br>    <strong>private</strong> <strong>let</strong> navigateToFinalFrom3 = FlowState()<br>    <strong>private</strong> <strong>let</strong> navigateToFinalFrom4 = FlowState()</pre><pre><strong>    init</strong>(delegate: FlowControllerViewDelegate) {<br>        <strong>self</strong>.delegate = delegate<br>    }</pre><pre><strong>    func</strong> navigate(to navigateTo: NavigateTo) {<br>        <strong>switch</strong> navigateTo {<br>        <strong>case</strong> .screen1: <strong>break<br>        case</strong> .screen2: navigateTo2.next = <strong>true<br>        case</strong> .screen3: navigateTo3.next = <strong>true<br>        case</strong> .screen4: navigateTo4.next = <strong>true<br>        case</strong> .finalFrom3: navigateToFinalFrom3 .next = <strong>true<br>        case</strong> .finalFrom4: navigateToFinalFrom4.next = <strong>true<br>        </strong>}<br>    }</pre><pre><strong>    var</strong> body: <strong>some</strong> View {<br>        NavigationView {<br>            VStack() {<br>                Screen(title: &quot;<strong>Screen 1</strong>&quot;, ...)<br>                Flow(state: navigateTo2) {<br>                    Screen(title: &quot;<strong>Screen 2</strong>&quot;, ...)<br>                    Flow(state: navigateTo3) {<br>                        BranchedScreen(title: &quot;<strong>Screen 3</strong>&quot;, ...)<br>                        Flow(state: navigateTo4) {<br>                            Screen(title: &quot;<strong>Screen 4</strong>&quot;, ...)<br>                            Flow(state: navigateToFinalFrom4) {<br>                                <strong>FinalScreen</strong>()<br>                            }<br>                        }<br>                        Flow(state: navigateToFinalFrom3) {<br>                            <strong>FinalScreen</strong>()<br>                        }<br>                    }<br>                }<br>            }<br>        }<br>    }<br>}</pre><pre><strong>struct</strong> Screen: View {<br>    <strong>let</strong> title: String<br>    <strong>let</strong> didTapNext: () -&gt; ()</pre><pre>    <strong>var</strong> body: <strong>some</strong> View {<br>        VStack() {<br>            Text(title)<br>            Button(<br>                action: { <strong>self</strong>.didTapNext() },<br>                label: { Text(&quot;Next&quot;) }<br>            )<br>        }<br>    }<br>}</pre><pre><strong>struct</strong> BranchedScreen: View {<br>    <strong>let</strong> title: String<br>    <strong>let</strong> didTapNextA: () -&gt; ()<br>    <strong>let</strong> didTapNextB: () -&gt; ()</pre><pre><strong>    var</strong> body: <strong>some</strong> View {<br>        VStack(alignment: .center) {<br>            Text(title)<br>            Button(<br>                action: { <strong>self</strong>.didTapNextA() },<br>                label: { Text(&quot;Next-A&quot;) }<br>            )<br>            Button(<br>                action: { <strong>self</strong>.didTapNextB() },<br>                label: { Text(&quot;Next-B&quot;) }<br>            )<br>        }<br>    }<br>}</pre><pre><strong>struct</strong> FinalScreen: View {<br>    <strong>var</strong> body: <strong>some</strong> View {<br>        VStack(alignment: .center) {<br>            Text(&quot;Final&quot;)<br>        }<br>    }<br>}</pre><p>In <a href="https://medium.com/@nicmcconn/flow-with-swiftui-and-mvvm-part-2-viewmodels-905ecc05f1c5">part 2</a> to follow we look at adding in screen-based ViewModels and completing all our <em>manifesto</em> goals.</p><p>Full code can be found: <a href="https://github.com/nickm01/NavigationFlow">https://github.com/nickm01/NavigationFlow</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7cc394440ab8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swlh/flow-with-swiftui-and-mvvm-7cc394440ab8">Flow with SwiftUI and MVVM</a> was originally published in <a href="https://medium.com/swlh">The Startup</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>