<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Write.as</title>
    <description>The latest articles on DEV Community by Write.as (@writeas).</description>
    <link>https://dev.to/writeas</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2996%2Fde0e1e4c-c1f0-463a-9385-202f9830a98f.png</url>
      <title>DEV Community: Write.as</title>
      <link>https://dev.to/writeas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/writeas"/>
    <language>en</language>
    <item>
      <title>Stupid SwiftUI Tricks: Debugging Sheet Dismissal</title>
      <dc:creator>Angelo Stavrow</dc:creator>
      <pubDate>Mon, 14 Sep 2020 18:49:01 +0000</pubDate>
      <link>https://dev.to/writeas/stupid-swiftui-tricks-debugging-sheet-dismissal-1p60</link>
      <guid>https://dev.to/writeas/stupid-swiftui-tricks-debugging-sheet-dismissal-1p60</guid>
      <description>&lt;p&gt;Last week, I spent some time solving an odd bug with the WriteFreely client's iOS app.&lt;/p&gt;

&lt;p&gt;When you're looking at the list of posts, you can tap the gear button to get to the settings screen, which presents you with a form for logging into your WriteFreely instance:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yNpIzguU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/nLAzPbZ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yNpIzguU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/nLAzPbZ.png" alt='"Screenshot of settings screen on an iPhone"'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you might expect, you can tap the close button in the upper-right (ⓧ) to dismiss the sheet.&lt;/p&gt;

&lt;p&gt;Except… well, if you tapped into one of the login form's fields, you end up in a state where tapping on the close button didn't seem to have any effect.&lt;/p&gt;

&lt;p&gt;Okay, so that's not entirely true — if you added a print statement to the button's action, you'd find that the first tap &lt;em&gt;does&lt;/em&gt; register, toggling presenting view's the &lt;code&gt;isPresentingSettingsView&lt;/code&gt; flag correctly; it just doesn't have any effect.&lt;/p&gt;

&lt;p&gt;The workaround, while I'd been testing the app, was to dismiss the sheet is by swiping down on it — a standard (if somewhat undiscoverable) system gesture.&lt;/p&gt;

&lt;p&gt;Interestingly, when you'd tap in any form field, you'd also receive the following warning in Xcode's console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2020-09-11 09:56:01.927435-0400 WriteFreely-MultiPlatform[37593:6860302] [Presentation] Attempt to present &amp;lt;_TtGC7SwiftUI22SheetHostingControllerVS_7AnyView_: 0x7fb24a7297f0&amp;gt; on &amp;lt;_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x7fb24c905ac0&amp;gt; (from &amp;lt;_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVVS_22_VariadicView_Children7ElementGVS_18StyleContextWriterVS_23ContentListStyleContext___: 0x7fb24a711ec0&amp;gt;) which is already presenting &amp;lt;_TtGC7SwiftUI22SheetHostingControllerVS_7AnyView_: 0x7fb24c80eaf0&amp;gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a lot of cruft there, but it hints that SwiftUI is trying to present a view that's already being presented. This suggested to me that the hosting view is getting re-rendered when a login form field becomes the first responder, finds that the &lt;code&gt;isPresentingSettingsView&lt;/code&gt; flag is set, and tries to present the sheet again.&lt;/p&gt;

&lt;p&gt;Okay! This is something we can test! Here's what the settings view looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SettingsView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@EnvironmentObject&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;WriteFreelyModel&lt;/span&gt;

    &lt;span class="kd"&gt;@Binding&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isPresented&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;VStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;HStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Settings"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;largeTitle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fontWeight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="kt"&gt;Spacer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isPresented&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;systemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"xmark.circle"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="kt"&gt;Form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;Section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Login Details"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;AccountView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="kt"&gt;Section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Appearance"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;PreferencesView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;preferences&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;preferences&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(For debugging purposes, I've simplified this a &lt;em&gt;tiny&lt;/em&gt; bit: originally that &lt;code&gt;HStack&lt;/code&gt; was in a separate &lt;code&gt;SettingsHeaderView&lt;/code&gt; struct.)&lt;/p&gt;

&lt;p&gt;To test the hypothesis, I started by commenting out the entire &lt;code&gt;Form&lt;/code&gt;. Everything then worked fine in presenting and dismissing the sheet, but of course, it's not a very useful sheet without that form. 😅&lt;/p&gt;

&lt;p&gt;If I just included the appearance form, that works fine too. That narrows things down here — or so I thought.&lt;/p&gt;




&lt;p&gt;There are two ways to dismiss a sheet. The first is to pass the hosting view's presentation state as a binding to the presented sheet, which is what you see in the above listing. Simplified, the &lt;code&gt;SettingsView&lt;/code&gt; is presented from the &lt;code&gt;PostListView&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isPresentingSettingsView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;systemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"gear"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;isPresented&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;isPresentingSettingsView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;SettingsView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;isPresented&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;isPresentingSettingsView&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also use @Environment(.presentationMode) in the &lt;code&gt;SettingsView&lt;/code&gt; to dismiss itself. You declare the property wrapper at the top of the struct like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@Environment&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;presentationMode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;presentationMode&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…and call its &lt;code&gt;dismiss()&lt;/code&gt; method in a button action, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;presentationMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wrappedValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;systemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"xmark.circle"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Interestingly enough, using this method to dismiss the sheet no longer triggered the console warning when I tapped into any login form field. Could it be? Was the problem solved? 😃&lt;/p&gt;

&lt;p&gt;Nope. 😬&lt;/p&gt;

&lt;p&gt;If you filled out the form and logged in, then that same warning was logged three times in the console. If you logged out, the warning was logged again. But this looked like progress! It seemed likely that something in the account views was triggering this, so I explored that a little deeper.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;AccountView&lt;/code&gt; swaps between an &lt;code&gt;AccountLoginView&lt;/code&gt; and an &lt;code&gt;AccountLogoutView&lt;/code&gt; based on the state of an &lt;code&gt;isLoggedIn&lt;/code&gt; flag in the &lt;code&gt;AccountModel&lt;/code&gt;. My prime suspect was the &lt;code&gt;AccountLoginView&lt;/code&gt;, which has an &lt;code&gt;.alert(isPresented:)&lt;/code&gt; modifier attached to it. If there's an error logging in, this is triggered and an alert is presented depending on which of the three &lt;code&gt;AccountError&lt;/code&gt; cases are present. Because the &lt;code&gt;.alert(isPresented:)&lt;/code&gt; and &lt;code&gt;.sheet(isPresented:)&lt;/code&gt; modifiers work similarly, maybe some wires were getting crossed there? This is, of course, a beta framework running on a beta operating system in a beta IDE!&lt;/p&gt;

&lt;p&gt;So, I started with an easy test: commenting out the &lt;code&gt;.alert(isPresented:)&lt;/code&gt; modifier, and see what happens on login. &lt;/p&gt;

&lt;p&gt;You guessed it: this doesn't change the behaviour — the warnings are still logged, and the sheet can't be dismissed.&lt;/p&gt;

&lt;p&gt;Digging further and further, setting breakpoints and stepping through code, commenting out blocks to see if they were the culprit, got me nowhere. I finally started searching DuckDuckGo for &lt;code&gt;SwiftUI "Attempt to present" "which is already presenting"&lt;/code&gt; and eventually found this &lt;a href="https://forums.swift.org/t/actionsheet-and-modal-seems-have-bug-in-navigationview/29590/10"&gt;year-old forum comment on Swift.org&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is it the current recommendation, to put &lt;em&gt;modal&lt;/em&gt; views &amp;amp; the triggers outside &lt;code&gt;NavigationView&lt;/code&gt;, or is it only to circumvent an existing bug?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🤦&lt;/p&gt;

&lt;p&gt;Yep. Taking the &lt;code&gt;.sheet(isPresented:)&lt;/code&gt; modifier out of the PostListView and attaching it to an EmptyView outside of the NavigationView solved the issue. Nothing in the docs on &lt;a href="https://developer.apple.com/documentation/swiftui/navigationview"&gt;NavigationView&lt;/a&gt;, &lt;a href="https://developer.apple.com/documentation/swiftui/navigationview-view-modifiers"&gt;View Modifiers&lt;/a&gt;, or &lt;a href="https://developer.apple.com/documentation/swiftui/navigationview/sheet(ispresented:ondismiss:content:)"&gt;sheet&lt;/a&gt; suggests this could be a thing.&lt;/p&gt;




&lt;p&gt;So, yeah, the title of this post is a bit misleading — it turns out that I spent a couple of hours trying to figure out what was happening, when an undocumented bug in the framework was the cause.&lt;/p&gt;

&lt;p&gt;Again: this is a beta framework, on a beta operating system, and frankly the amount of SwiftUI documentation that's already out there is surprisingly good. But it's a little frustrating to have spent a couple of hours debugging a warning that could have been avoided with a one-line disclaimer in the documentation. Hopefully, this will be helpful to anyone that searches for a similar issue!&lt;/p&gt;

&lt;p&gt;For those of you that want to see the code, &lt;a href="https://github.com/writeas/writefreely-swiftui-multiplatform/commit/6ea10fb434393fd5a19f34f35a9c768617eb32cc"&gt;here's the fix&lt;/a&gt; in the app.&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>swift</category>
      <category>ios</category>
      <category>showdev</category>
    </item>
    <item>
      <title>WriteFreely Mac/iOS App: A Progress Update</title>
      <dc:creator>Angelo Stavrow</dc:creator>
      <pubDate>Fri, 28 Aug 2020 20:21:58 +0000</pubDate>
      <link>https://dev.to/writeas/writefreely-mac-ios-app-a-progress-update-15o8</link>
      <guid>https://dev.to/writeas/writefreely-mac-ios-app-a-progress-update-15o8</guid>
      <description>&lt;p&gt;It feels like it was just yesterday when I &lt;a href="https://dev.to/angelostavrow/the-promise-vs-reality-of-swiftui-multiplatform-4pf9"&gt;shared some initial screenshots&lt;/a&gt; of the WriteFreely app prototype running on iOS and Mac.&lt;/p&gt;

&lt;p&gt;(In fact, it was almost four weeks ago.)&lt;/p&gt;

&lt;p&gt;I've been making a lot of progress on this, and I'd love to show you where the app is today.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pictures, Worthy of A Thousand Words
&lt;/h2&gt;

&lt;p&gt;How about starting with a few screenshots? This is subject to change, but it's likely not going to change &lt;em&gt;much&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vfeHuJPZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/HLSuQGs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vfeHuJPZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/HLSuQGs.png" alt='"iPhones and iPad showing various screenshots of the WriteFreely app"'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above, you'll see the current version of the app, running on iPhone and iPad.&lt;/p&gt;

&lt;p&gt;The first iPhone screenshot shows the list of posts in my &lt;em&gt;Testing&lt;/em&gt; collection, a private blog which I'm using for —you guessed it— testing. Tapping on the &lt;strong&gt;&amp;lt; Collections&lt;/strong&gt; navigation link at the top-left will let you choose another collection; other buttons above and below the list let you create a new local post, refresh from the server, and access settings and preferences.&lt;/p&gt;

&lt;p&gt;Tap on any post in the list, and you'll be taken to the app's editor, as shown in the second iPhone screenshot. Any edits you make here are saved locally, and if you like, you can publish it to your WriteFreely instance.&lt;/p&gt;

&lt;p&gt;The iPad screenshot shows the same navigation hierarchy, and like the iPhone version, it works just as you'd expect on iPadOS — a sidebar to choose the collection, a table to choose a post in the collection, and the selected post in the editor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q_8sli4i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/JlLaWv2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q_8sli4i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/JlLaWv2.png" alt='"The WriteFreely app running on the Mac"'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the app running on the Mac — the same kind of navigation, as suited to the Mac. You can more clearly see how the app's editor departs a bit from the web editor, too, with a defined area for the post's title (if you choose to add one), and another text area below for your post's content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WCmD4EId--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/wDaM902.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WCmD4EId--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/wDaM902.png" alt="&amp;quot;The WriteFreely app in 'dark mode' on iPhone and Mac&amp;quot;"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oh, and of course, "dark mode" looks great. You can choose to have the app display a light scheme, a dark scheme, or to simply follow your system's setting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Will It Post?
&lt;/h2&gt;

&lt;p&gt;The basic flow of the app right now is straightforward. On launch, you're not logged in to your account, but you can create new posts that only live on your device. These posts currently show a "draft" badge, though that's going to &lt;a href="https://github.com/writeas/writefreely-swiftui-multiplatform/issues/36"&gt;get a less-confusing name&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As of the time of writing, the app doesn't yet store anything on disk except for your colour-scheme preference and your logged-in state, so if you quit the app —or if it crashes, which at this stage, it does every so often— you lose whatever you had. So, naturally, &lt;a href="https://github.com/writeas/writefreely-swiftui-multiplatform/issues/16"&gt;adding local storage&lt;/a&gt; is a priority.&lt;/p&gt;

&lt;p&gt;To guard against that, you can log in to your WriteFreely account to pull in your collections and posts from the server. Those posts show up as "published", and you can edit them, at which point the "published" badge changes to "edited"; when you're done, you can push those changes to the server, which has the "edited" badge go back to "published".&lt;/p&gt;

&lt;p&gt;Similarly, your local posts (the ones with the "draft" badge) can be published to your WriteFreely Drafts —see why it needs to be renamed?— at which point the badge changes to "published".&lt;/p&gt;

&lt;p&gt;Tapping the reload button in the post list will fetch new content from the server. Because data loss is unacceptable, the app checks to see what the last-updated timestamps are between the local and remote copies of a post, and then applies the following rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If the local copy is unchanged and the remote copy is newer, replace the local copy with the remote copy;&lt;/li&gt;
&lt;li&gt;If the local copy has unpublished changes and the remote copy is older, discard the remote copy; and finally,&lt;/li&gt;
&lt;li&gt;If the local copy has unpublished changes and the remote copy is newer, notify the user and prompt them to make a decision:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vPvmJ5eh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/XG1RChc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vPvmJ5eh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/XG1RChc.png" alt='"A portion of a screenshot warning that a newer copy of the post exists on the server."'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This last bit is still under construction. Once the work is complete, you'll be able to choose which copy to keep — and of course, you'll be prompted to confirm your choice, since this will replace the local copy with the remote copy, or vice-versa.&lt;/p&gt;

&lt;p&gt;If you log out of your account, everything is purged from the device — your collection list, your local &lt;em&gt;and&lt;/em&gt; published posts, your logged in state, everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Still To Do
&lt;/h2&gt;

&lt;p&gt;The easiest thing would be just link to the &lt;a href="https://github.com/writeas/writefreely-swiftui-multiplatform/issues?q=is%3Aopen+is%3Aissue+milestone%3A1.0.0-beta"&gt;list of GitHub issues for the 1.0 beta&lt;/a&gt; — but I'm going to discuss some of the more pertinent things here, to save you a click.&lt;/p&gt;

&lt;p&gt;Of course, saving content to your device is going to be important. There are also a couple of bugs that need fixing because they hang the app or make buttons unresponsive, so that's no good. But mostly, these to-do items fix get the user experience close to that of the WriteFreely web app.&lt;/p&gt;

&lt;p&gt;For example — right now the app launches and drops you in the post list. It should instead drop you in the editor with a new (or recent) local post. Those local posts default to Drafts (i.e., not part of a collection), but you should be able to choose a collection for them.&lt;/p&gt;

&lt;p&gt;Departing from other WriteFreely clients, there's an "All Posts" list which, as the name implies, shows all posts across your Drafts and your collections, all together. It doesn't, however, tell you which post belongs to which collection (neither does the editor), so that needs fixing.&lt;/p&gt;

&lt;p&gt;And there are a few things that haven't been added as issues just yet —and maybe they won't make it into the first release— that include, off the top of my head:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sorting the list (by created date or last-updated date, ascending or descending)&lt;/li&gt;
&lt;li&gt;Changing the logout behaviour to keep only your local posts, and adding another setting to delete everything (the current logout behaviour)&lt;/li&gt;
&lt;li&gt;Full-text search&lt;/li&gt;
&lt;li&gt;Keyboard shortcuts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're getting closer and closer to a public beta, so that's exciting! And if you want to &lt;a href="https://write.as/angelo/contributing-to-the-writefreely-swiftui-client"&gt;contribute&lt;/a&gt;, please don't hesitate to &lt;a href="https://discuss.write.as/t/writefreely-swiftui-multiplatform-app-mac-iphone-ipad/1664"&gt;reach out in this forum topic&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>swift</category>
      <category>swiftui</category>
      <category>writefreely</category>
      <category>showdev</category>
    </item>
    <item>
      <title>
The Promise vs Reality of SwiftUI Multiplatform</title>
      <dc:creator>Angelo Stavrow</dc:creator>
      <pubDate>Mon, 03 Aug 2020 20:16:37 +0000</pubDate>
      <link>https://dev.to/writeas/the-promise-vs-reality-of-swiftui-multiplatform-4pf9</link>
      <guid>https://dev.to/writeas/the-promise-vs-reality-of-swiftui-multiplatform-4pf9</guid>
      <description>&lt;p&gt;Happy August, friends! How about an update to &lt;a href="https://dev.to/angelostavrow/writefreely-swiftui-client-35l2"&gt;last week's post&lt;/a&gt; on the SwiftUI app for WriteFreely?&lt;/p&gt;

&lt;p&gt;Work progressed pretty well on the shared code last week, to the point that we've got a preliminary UI that displays a list of posts, which you can filter by collection. Tapping on any post loads it into an editor, where you can make changes to the title and content.&lt;/p&gt;

&lt;p&gt;On iPhone and iPad, there's also a new post button that will create a new draft. Why only on iPhone and iPad? Well, pull up a chair, and let's chat a little bit about the gap between the &lt;em&gt;promise&lt;/em&gt; and the &lt;em&gt;reality&lt;/em&gt; of multi-platform SwiftUI apps.&lt;/p&gt;

&lt;p&gt;While I was working on the post, I &lt;a href="https://twitter.com/AngeloStavrow/status/1287828639527182337"&gt;tweeted&lt;/a&gt; that I managed to flesh out some of the key UI features for the app across iPhone, iPad, and Mac in &lt;em&gt;a day&lt;/em&gt;, thanks to SwiftUI and live previews in Xcode 12, which show you what your views will look like next to your code, and update in realtime &lt;em&gt;as&lt;/em&gt; you code.&lt;/p&gt;

&lt;p&gt;I further stated:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is building a native app with the speed of iteration that I've only ever encountered when building for the web.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(I'm looking at you, HTML and CSS. I am &lt;em&gt;not&lt;/em&gt; looking at you, React.)&lt;/p&gt;

&lt;p&gt;So here's what that latest iteration looks like so far, on iPhone and iPad:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4oXdnOet--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/32UKJhY.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4oXdnOet--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/32UKJhY.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And, after I installed the macOS 11 (a.k.a. "Big Sur") beta, I was able to build the Mac app to compare:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--He1Te_oL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/KT6IOMu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--He1Te_oL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/KT6IOMu.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is in no way a final UI! There's plenty of work still to be done, including replacing the unusual "picker" view for selecting the Collection with a proper sidebar, and adapting the spacing a little better on the Mac app (you can't preview anything for a Mac target, even in Xcode, until you upgrade your own machine to the macOS 11 beta).&lt;/p&gt;

&lt;h2&gt;
  
  
  The DX of SwiftUI, as illustrated by &lt;code&gt;ToolbarItemPlacement&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;One thing that's very helpful in moving quickly with SwiftUI is how its declarative nature lets you specify the &lt;em&gt;semantics&lt;/em&gt; of an element's placement in your view, and letting the framework figure out what that should mean on iPhone vs iPad vs Mac. A great example of this focus on developer experience (DX) is the &lt;a href="https://developer.apple.com/documentation/swiftui/toolbaritemplacement"&gt;&lt;code&gt;ToolbarItemPlacement&lt;/code&gt;&lt;/a&gt; API, which lets you semantically state that a particular item that you add to a toolbar is a &lt;code&gt;.primaryAction&lt;/code&gt;, or a &lt;code&gt;.status&lt;/code&gt; element, and so on.&lt;/p&gt;

&lt;p&gt;This is used in the TextEditor view to place a post status badge in the &lt;code&gt;.status&lt;/code&gt; area and a publish button in the &lt;code&gt;.primaryAction&lt;/code&gt; area. You can see in the screenshots I shared above how these elements are placed differently depending on whether it's being shown on an iPhone, an iPad, or a Mac — and I didn't have to write a single line of code to customize the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Letdown of SwiftUI, as illustrated by broken buttons
&lt;/h2&gt;

&lt;p&gt;But life in SwiftUI is not all roses. And that's where the new-draft feature isn't quite working fully, yet. Building the iPhone and iPad versions of the app, tapping the new-draft button above the list of posts works beautifully — it slides a new draft into the list, and then opens it in the text editor for your next great essay. I was thrilled to have this working by the end of my workday on Friday.&lt;/p&gt;

&lt;p&gt;I finished installing the Big Sur beta on my Mac this morning, excited to see how everything looked and worked in the Mac app. I expected to have some little UI refinements (that list of posts, for example, needs a healthy dose of padding), but otherwise figured that it'd be a similar experience to, at the very least, the iPad app.&lt;/p&gt;

&lt;p&gt;And mostly, it is. The picker is replaced by a popup menu, and you can choose a collection to narrow down the list of posts. You can tap a post in the list, and it'll load it in the text editor, ready for changes. If the post's status changes, it'll instantly update the badge both in the toolbar and in the post list.&lt;/p&gt;

&lt;p&gt;And then I tried to create a new draft. On the Mac, clicking on that new-draft button (which appears, interestingly enough, next to the post-status badge in the screenshot) does… nothing. Not a thing. No errors are thrown, no views are drawn, nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reality of Multiplatform
&lt;/h2&gt;

&lt;p&gt;I suppose I shouldn't use the word &lt;em&gt;letdown&lt;/em&gt;, because "multiplatform" is &lt;em&gt;not&lt;/em&gt; a panacea: the reality is that an app expects different things between an iPhone, an iPad, and a Mac. The issue I'm facing with that new-draft button isn't some failing of SwiftUI; it's that the paradigm for how a Mac app treats navigation vs buttons vs instantiating new windows is very different from that of an iPhone, and I haven't structured things the way the framework expects.&lt;/p&gt;

&lt;p&gt;So, fine. I'll need to spend a little more time on adapting the Mac client. I would have &lt;em&gt;liked&lt;/em&gt; it if it all Just Worked™, but I've got to say: despite a couple of little issues that I need to fix, I'm really impressed with just how much of it worked right out of the box, with no extra work on my part.&lt;/p&gt;

&lt;p&gt;Native-app development may not yet happen at the pace of web development, but it's closer than ever.&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>showdev</category>
      <category>writefreely</category>
    </item>
    <item>
      <title>WriteFreely SwiftUI Client</title>
      <dc:creator>Angelo Stavrow</dc:creator>
      <pubDate>Mon, 27 Jul 2020 17:00:29 +0000</pubDate>
      <link>https://dev.to/writeas/writefreely-swiftui-client-35l2</link>
      <guid>https://dev.to/writeas/writefreely-swiftui-client-35l2</guid>
      <description>&lt;p&gt;Hot on the heels of the developer-preview release of the &lt;a href="https://write.as/angelo/writefreely-swift-package-v0-1-0-released"&gt;WriteFreely Swift package&lt;/a&gt;, I'm happy to announce that work has started on a new (but related) project: a WriteFreely multiplatform SwiftUI client app.&lt;/p&gt;

&lt;h2&gt;
  
  
  A multiwhatsit who now?
&lt;/h2&gt;

&lt;p&gt;Right. Let's clear up the jargon, first.&lt;/p&gt;

&lt;p&gt;Apple announced &lt;a href="https://developer.apple.com/xcode/swiftui/"&gt;SwiftUI&lt;/a&gt; at their World Wide Developer Conference (WWDC) last year. Initially, SwiftUI let you build user interfaces across all Apple platforms —iPhone, iPad, Mac, Watch, and Apple TV— with a single, declarative framework, ostensibly replacing the need for using AppKit (on the Mac) and UIKit (on iOS) in building your apps.&lt;/p&gt;

&lt;p&gt;This year, Apple took SwiftUI further: you can now build entire apps in SwiftUI. And while you can still build a platform-specific app (e.g., just for the Mac, or just for Apple TV), a new template named &lt;em&gt;multiplatform&lt;/em&gt; further coalesces developing for the Apple ecosystem.&lt;/p&gt;

&lt;p&gt;A multiplatform app project in Xcode contains a &lt;strong&gt;Shared&lt;/strong&gt; folder, in which the bulk of your app's code lives. There are also &lt;strong&gt;iOS&lt;/strong&gt; and &lt;strong&gt;macOS&lt;/strong&gt; folders for platform-specific customizations, but the idea is that you can use SwiftUI components to build an app that will run natively on iPhone, iPad, and Mac with almost no such customization necessary.&lt;/p&gt;

&lt;p&gt;That's pretty cool.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, what exactly is being built?
&lt;/h2&gt;

&lt;p&gt;This is a WriteFreely client app. You'll be able to log in to your WriteFreely instance, write new drafts, publish them to a collection, and edit them, thanks to the WriteFreely Swift package. And it'll be available for iPhone, iPad, and Mac, thanks to SwiftUI.&lt;/p&gt;

&lt;p&gt;Just like the Swift package, it'll be built in the open — the repository is &lt;a href="https://github.com/writeas/writefreely-swiftui-multiplatform"&gt;on GitHub&lt;/a&gt;, and if you're interested in helping, check out the &lt;a href="https://github.com/writeas/writefreely-swiftui-multiplatform/blob/main/CONTRIBUTING.md"&gt;CONTRIBUTING&lt;/a&gt; doc. Developers of all experience levels are welcome to join — just let me know in &lt;a href="https://discuss.write.as/t/writefreely-swiftui-multiplatform-app-mac-iphone-ipad/1664"&gt;this forum topic&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;The goal is to go from a new SwiftUI project to a complete working app in time for the release of iOS 14 and macOS 11. The launch usually happens in September, so we want to have the app done by the end of August. That's a tight deadline, so we're limiting the scope initially to handle the key actions WriteFreely users would want: creating drafts, publishing them to their WriteFreely instance (either as a draft, or to a collection), and updating those posts.&lt;/p&gt;

&lt;p&gt;As we build the client app, we'll be updating the Swift package as necessary. As far as I know, this will be the first WriteFreely client to consume the package, so I expect that we'll find a few surprises that trip us up as we go along. While I tried to build it as robustly as possible, that's the nature of packages that wrap an API — you only find out about their shortcomings when you start building projects with them! 😅&lt;/p&gt;

&lt;p&gt;I'll be posting here on both the progress of the work, and the learnings that inevitably come from building a new app in a beta IDE, against a beta framework, while running beta operating systems. I've already found one UX deficiency in &lt;a href="https://angelostavrow.com/post/using-swift-packages-in-multiplatform-swiftui-apps/"&gt;adding packages to multiplatform apps&lt;/a&gt;, and I'm sure there will be more surprises to share.&lt;/p&gt;

&lt;p&gt;More TK!&lt;/p&gt;

</description>
      <category>writefreely</category>
      <category>swift</category>
      <category>swiftui</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
