<?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:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[The Dodo Developer]]></title><description><![CDATA[Saving app ideas from extinction]]></description><link>https://www.thedododeveloper.com</link><image><url>https://substackcdn.com/image/fetch/$s_!QhOr!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F01053aff-02c1-4257-bfff-6177a718eb98_512x512.png</url><title>The Dodo Developer</title><link>https://www.thedododeveloper.com</link></image><generator>Substack</generator><lastBuildDate>Thu, 23 Apr 2026 12:01:17 GMT</lastBuildDate><atom:link href="https://www.thedododeveloper.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Ben Dodson]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[ben@bendodson.com]]></webMaster><itunes:owner><itunes:email><![CDATA[ben@bendodson.com]]></itunes:email><itunes:name><![CDATA[Ben Dodson]]></itunes:name></itunes:owner><itunes:author><![CDATA[Ben Dodson]]></itunes:author><googleplay:owner><![CDATA[ben@bendodson.com]]></googleplay:owner><googleplay:email><![CDATA[ben@bendodson.com]]></googleplay:email><googleplay:author><![CDATA[Ben Dodson]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[#10: The First Spatial Computer]]></title><description><![CDATA[You didn't really think they'd call it a headset did you?]]></description><link>https://www.thedododeveloper.com/p/10-the-first-spatial-computer</link><guid isPermaLink="false">https://www.thedododeveloper.com/p/10-the-first-spatial-computer</guid><pubDate>Wed, 07 Jun 2023 13:45:45 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/89fea53b-19d7-4c24-aa1b-77cd91730584_3840x2160.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dear Reader,</p><p>After many years of speculation and waiting, Apple have <em>finally</em> unveiled Vision Pro, their first &#8220;VR headset&#8221;. Except it&#8217;s not a VR headset; it&#8217;s a <em>Spatial Computer</em>. Whilst this might just feel like marketing language, I think it definitely helps frame how new this type of product really is.</p><p>This isn&#8217;t something that is tethered to your iPhone or your Mac; it&#8217;s a full computer running with an M2 chip, the same as the most recent Mac laptops and desktops. It also isn&#8217;t VR, that&#8217;s just a small use case within it. Instead, the default mode is Augmented Reality showing you a window based interface within you own world complete with some truly astonishing effects such as shadows, reflections, and realtime blurring to make it feel grounded in the real world. You can go into a full VR mode but this seems likely to be the exception rather than the rule.</p><p>I was particularly drawn to this demo of the Keynote app in the <a href="https://developer.apple.com/wwdc23/10072">Principles of spatial design talk</a> yesterday:</p><blockquote><p>In Keynote, the app opens in a window. But when it's time to play the slideshow, we use dimming to bring focus to this presentation. Dimming is a simple way to create contrast between your content and people's surroundings without taking them out of their space. When it's time to rehearse the presentation, we can bring people onto the big stage fully immersing them within the theater<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>. Life-size experiences like these require more room, so Keynote is now in a Full Space and other apps are hidden.</p></blockquote><p>You can use the app in a window placed in the real world but when you start to play the presentation <strong>the world gets dimmed</strong> to focus on the content. This is insane!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UETy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa764a7-babf-4d1d-84ff-c9d5f9dc3a00_5320x3080.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UETy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa764a7-babf-4d1d-84ff-c9d5f9dc3a00_5320x3080.jpeg 424w, https://substackcdn.com/image/fetch/$s_!UETy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa764a7-babf-4d1d-84ff-c9d5f9dc3a00_5320x3080.jpeg 848w, https://substackcdn.com/image/fetch/$s_!UETy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa764a7-babf-4d1d-84ff-c9d5f9dc3a00_5320x3080.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!UETy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa764a7-babf-4d1d-84ff-c9d5f9dc3a00_5320x3080.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UETy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa764a7-babf-4d1d-84ff-c9d5f9dc3a00_5320x3080.jpeg" width="1456" height="843" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5aa764a7-babf-4d1d-84ff-c9d5f9dc3a00_5320x3080.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:843,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:793691,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UETy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa764a7-babf-4d1d-84ff-c9d5f9dc3a00_5320x3080.jpeg 424w, https://substackcdn.com/image/fetch/$s_!UETy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa764a7-babf-4d1d-84ff-c9d5f9dc3a00_5320x3080.jpeg 848w, https://substackcdn.com/image/fetch/$s_!UETy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa764a7-babf-4d1d-84ff-c9d5f9dc3a00_5320x3080.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!UETy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa764a7-babf-4d1d-84ff-c9d5f9dc3a00_5320x3080.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>That a device can not only insert content into the real world but then selectively remove bits or have a half-way mode where it&#8217;s dimmed is an astonishing feat of engineering. Another impressive demonstration was a mindfulness exercise in which the room is dimmed as you view a pulsating sphere; as the sphere expands with your intake of breath, the room disappears into darkness only to return as you breathe out. Incredible.</p><p>Another facet that I find utterly fascinating is the way presence is managed with EyeSight:</p><blockquote><p>An outward display reveals your eyes while wearing Vision&nbsp;Pro, letting others know when you are using apps or fully immersed.</p></blockquote><p>It had been rumoured that there was an external display to show your facial expression but this had been blown off for being unnecessary and a drain on a presumably constrained battery. It turns out that it is very real and, to my mind, is one of the most well thought out features of the device. In fact, it was referred to in the Keynote as a &#8220;foundational design goal&#8221;:</p><blockquote><p>Another foundational design goal for Vision Pro was that you're never isolated from the people around you. You can see them, and they can see you. Your eyes are a critical indicator of connection and emotion, so Vision Pro displays your eyes when someone is nearby. Not only does EyeSight reveal your eyes, if provides important cues to others about what you're focused on.</p></blockquote><p>Vision Pro has a curved OLED panel with a lenticular lens creating a faux depth effect so your eyes look correct no matter which angle somebody is looking at you from. I love that your eyes get obscured if you&#8217;re looking at windows within Augmented Reality and that the entire display gets covered in a fog when you&#8217;re within a full screen Virtual Reality experience. It makes it incredibly clear to anyone around you as to whether you can see them or not.</p><p>This goes the other way in that if somebody comes close to you whilst you have the headset on a blended cutout appears so <em>you</em> can see them. Whilst switching on external cameras on a VR headset so you can see has been around for a while<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> this is a whole new level.</p><p>I saw <a href="https://mastodon.social/@rasmusnielsen/110469888753033390">a mockup</a> a few days before WWDC of Tim Cook wearing a VR headset with his Memoji on the front. This seemed semi-plausible to me as I didn&#8217;t think there would be enough cameras internally to replicate the user&#8217;s eyes. Memoji also seemed like the way one would appear on FaceTime calls. Instead, Vision Pro scans your face with it&#8217;s spatial camera during setup and then creates an uncanny valley 3D model of you which is then animated. This is the bit that looks the most odd to me but I&#8217;m excited to see how well it will work in practice. Regardless, it&#8217;s telling that Apple have gone for a real world look rather than a cartoon one clearly pitching this product as a professional device rather than a novelty.</p><p>It was rumoured for a long time that this headset was going to be around $3000. Some expected it to be far cheaper in a twist like the original iPad announcement<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> but instead it&#8217;s actually a bit more <em>starting at </em>$3500. I expect that &#8220;starting at&#8221; bit is due to it either having multiple storage capacities or to cover those of us that need to buy the magnetic prescription lenses which I fully expect to be at least $250 per lens. Many look at that and think this is a ridiculous price for a VR headset, and they&#8217;d be right; but this isn&#8217;t a VR headset. I personally think $3500 is an absolute steal for this level of hardware combining an M2 computer with over two 4K screens worth of pixels along with seamless blending of virtual and actual reality with no hand controllers required<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>. It&#8217;s incredibly reassuring that the <a href="https://arstechnica.com/gadgets/2023/06/hands-on-with-apple-vision-pro-this-is-not-a-vr-headset/">hands-on reviews</a> coming out of WWDC are all saying it works as shown; the keynote was not a CGI mockup or aspirational demo but an actual, real device.</p><p>Whilst there are some videos from WWDC showing off various facets of Vision Pro and how to develop apps for it, a full SDK won&#8217;t be available until later this month. In terms of developing for the headset, there is a simulator (which looks pretty good) but you can also go to six different locations around the world to test your apps or you can send a build to Apple and they&#8217;ll try it out and let you know if they run into any problems. Apple has also announced that <a href="https://developer.apple.com/visionos/work-with-apple/">developer kits will be available</a> with more details coming soon. I&#8217;ll certainly be aiming to get one.</p><p>The age of spatial computing has begun&#8230; and I cannot wait!</p><p><strong>&#8212; Ben</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/10-the-first-spatial-computer/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/10-the-first-spatial-computer/comments"><span>Leave a comment</span></a></p><h1>Contents</h1><ul><li><p>What&#8217;s New at WWDC 2023</p></li><li><p>The Spatial Audio Database</p></li><li><p>The Lossless Audio Database</p></li><li><p>ChatGPT as an Intern Developer</p></li><li><p>Recommendations</p></li><li><p>Roadmap</p></li></ul><h1>What&#8217;s New at WWDC 2023?</h1><p>In the last issue, I mentioned in my intro:</p><blockquote><p>In the indie development space, the launch of a new version of iOS is the time that getting a coveted featured spot from Apple or the wider blogosphere is more likely <em>if</em> you can add some new feature to your app related to the iOS update. The problem with this is that you typically get about 2 months to come up with this idea and incorporate it into your app whilst dealing with the multitude of bugs in the beta software and the lack of concrete documentation. It can be a stressful time!</p></blockquote><p>Fortunately it&#8217;s <em>not</em> that much of a stressful time this year as there isn&#8217;t much in the software updates that I&#8217;m keen to explore. There are certainly a couple of interesting tidbits but no major new feature that will get me rushing to get something into an app in time for the assumed September release of iOS 17.</p><p>Rather than building something, I thought I&#8217;d run through a few of the things that have caught my eye from the Keynote, State of the Union, updated documentation, and actually using the new software.</p><h2>iOS 17</h2><p>I always find it hard to predict what new features are going to appear in iOS as the low hanging fruit has long since gone. There are very few areas I look at and think &#8220;this needs changing&#8221; and there isn&#8217;t anything in this release that has made me go &#8220;wow, I wish I&#8217;d thought of that&#8221;. This is not necessarily a bad thing; I&#8217;d much rather have ten or twenty items minor improvements than one big flashy feature.</p><ul><li><p>Call Screen Personalisation - When this was first showcased in the keynote it looked like a user could create a custom call screen that would then be used on other users devices. This obviously has some bad use cases and wasn&#8217;t something I was keen on having inflicted upon my device. Fortunately it&#8217;s not quite as hard cut as it looked. The system piggybacks on the existing contact card customisation (where you change your avatar and it suggests to people with you in their contacts that they accept the update) and you can create custom call screens for all of your contacts if you want to. The UI is pretty much identical to the lock screen stuff that was added in iOS 16.</p></li><li><p>Live Voicemail - True story: Visual Voicemail was the feature that convinced me to buy an iPhone when they first launched. This seems like a logical next step and doesn&#8217;t involve carrier support which Visual Voicemail still requires.</p></li><li><p>Messages - A lot of &#8220;sherlocking&#8221; in the messages app as <a href="https://apps.apple.com/gb/app/sticker-drop/id1629369500">Sticker Drop</a> gets it&#8217;s <em>raison d'&#234;tre</em> removed and the Check In feature destroys an entire category of apps. My personal favourite is audio message transcription; my wife often leaves me audio messages (which I despise - they&#8217;re so interruptive) so having them automatically transcribed is a big win for me even though it comes at the expense of <a href="https://goodsnooze.gumroad.com/l/scusi">Scusi</a> on macOS which I&#8217;ve been using up until now. I&#8217;ve also noticed that the location part for family members has been redesigned so iOS now shows you not only where they are but how long it would take you to get to them.</p></li><li><p>Live Video reactions - The first client that uses the thumbs up reaction to produce fireworks in a meeting with me is going to get hung up on.</p></li><li><p>Standby - I can see this being really useful for a lot of people. Unfortunately, I have a MagSafe Duo on my bedside table and this feature only works when the device is standing up in landscape mode. I also don&#8217;t really want this information in my bedroom, especially when I have an Apple Watch on my wrist whilst I sleep with the screen turned off. The stand that they&#8217;re showing off in all the promo art for this is not available yet but presumably will be in September. Personally I see this as a preview of what a HomePod with a screen is going to look like&#8230;</p></li><li><p>Interactive Widgets - Bizarre that this hasn&#8217;t come sooner (especially given that Apple Music has an interactive widget on Android) but an easy win. I can see the benefit for the <a href="https://culturedcode.com/things/">Things</a> widgets that I use and I have a client that is excited to get them into their reminders app but I can&#8217;t say I&#8217;m overly excited about them.</p></li><li><p>AirDrop - The NameDrop feature is cute (I had a pitch given to me for something like this back in the iPhoneOS 3.0 days) but the ability to AirDrop and have it continue over the internet if you step away is the more useful feature to me.</p></li><li><p>Journal - I haven&#8217;t been able to play with this as it isn&#8217;t coming until later this year but from the screenshots provided it doesn&#8217;t look like a big game changer for these types of apps. Rather than killing off apps like Day One I suspect this will actually boost downloads for more powerful journalling apps<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>.</p></li><li><p>Keyboard - The new autocorrect feature works as advertised. I&#8217;ve definitely noticed an improvement over the past couple of days.</p></li><li><p>Autofill verification codes - You can now get autofill from email but even better the system can now delete those SMS and emails after the code has been used. A beautiful feature!</p></li><li><p>Music - Very little has changed in the Music app except the toolbar at the bottom has got a bit of a Vision Pro vibe added to it, animated album covers (available in my <a href="https://bendodson.com/projects/apple-music-artwork-finder/">Apple Music Artwork Finder</a>) now show during fullscreen playback, and there is a collaborative playlist feature coming later in the year. A bit of a disappointment that Spatial Audio and Lossless Audio still don&#8217;t have good discovery tools but that&#8217;ll keep me in business for another year &#128514;</p></li><li><p>PDFs - Not what I was expecting, but PDFs can now be autofilled. When this was first demoed I assumed it was for PDFs with actual coded forms but it became clear it also worked on scanned documents and is instead using all the fancy Text and Vision APIs. Whilst this was demoed in the iPad section of the keynote, it&#8217;s available on iPhone as well.</p></li></ul><h2>iPadOS 17</h2><p>If iOS doesn&#8217;t have much going on this year, then iPadOS has even less. Apple seem to have taken a break this year from trying to create a new way to multitask and instead we&#8217;re left with two new features which both appeared in iOS in previous years:</p><ul><li><p>Lock Screen - Everyone knew it was coming as there was a way to trick it into appearing in one of the iOS 16 betas. It&#8217;s got a new size of widget but there isn&#8217;t anything particularly exciting here in my opinion.</p></li><li><p>Health - 9 years after appearing on iPhone, the iPad now gets access to the Health app. Hopefully we&#8217;ll see it on macOS before 2042!</p></li></ul><h2>macOS Sonoma</h2><p>When your headline feature is &#8220;new screensavers&#8221; it&#8217;s safe to say we&#8217;re in what people still refer to as a &#8220;Snow Leopard&#8221; year of bug fixes. The large majority of iOS features make their way across but there is also new support for widgets:</p><ul><li><p>These can now be added to the desktop along with a nice animation to fade them out when you&#8217;re focussing on windows on top. The API for this is also very easy to implement and they get the interactivity that was added to iOS / iPadOS.</p></li><li><p>More impressive was that you can add iPhone widgets to your Mac without installing the relevant app! I was expecting this to be an Apple Silicon &#8220;install an iOS app and its widgets will magically work&#8221; but nope, they&#8217;re widgets updated over the network thanks to the technology behind hand-off. Really clever and might make me more likely to add a couple of widgets to my desktop.</p></li></ul><h2>watchOS 10</h2><p>It was rumoured that watchOS 10 was going to get a big redesign and so it has come to pass&#8230; sort of. Apple are calling them &#8220;comprehensive app redesigns&#8221; but they aren&#8217;t radically different. That is aside from the Stopwatch app which now has a full white background completely killing the illusion of the watch fading into the bezels; <a href="https://mastodon.bendodson.com/@ben/110498027857817620">it looks awful</a>. Full colour backgrounds seem to be the thing for several apps and I just can&#8217;t get behind it. Apple&#8217;s marketing uses the phrase &#8220;apps that leave no corner untouched&#8221; and they&#8217;re right; the UI is touching all the corners &#129326;</p><p>Aside from that there was a Snoopy watch face<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a> and some improvements for cyclists and hiking but not a whole lot else. Hopefully some new hardware in September will unlock some further improvements!</p><h2>Other products</h2><ul><li><p>tvOS - There wasn&#8217;t enough for a dedicated segment but one new feature tvOS did get was the ability to make FaceTime calls thanks to Continuity Camera with an iPhone or iPad. I really like this and again think it&#8217;s a play for when a HomePod gets a screen at some point. The ability to &#8220;find&#8221; your Siri Remote is also something that has been on wishlists for a while and amazingly doesn&#8217;t require new hardware.</p></li><li><p>CarPlay - I&#8217;ve noticed a few bits of UI tweaking but there is also the option to let other iPhone users in your car set the music without having to use your phone. I come from of a philosophy of &#8220;who&#8217;s driving gets to choose the music&#8221; so I&#8217;ll pass on that &#129315;</p></li><li><p>AirPods - A couple of nice feature for AirPods Pro 2 including Adaptive Audio (to seamlessly transition between noise cancellation and transparency mode) and Conversation Awareness (auto-ducking of audio when someone speaks to you). Nice to see AirPods getting software updates between hardware refreshes for features that could easily have been held back.</p></li><li><p>AirTags - Not announced in the keynote but it surely would have got a standing ovation if it was; AirTags can now be shared with your family. No longer will it look like my wife is stalking me when I take her car keys &#128514;</p></li></ul><h2>Frameworks</h2><ul><li><p>Swift Macros - I don&#8217;t like abstracting things away with magic, so it&#8217;s very clever that Xcode can expand macros in place so you can clearly see how they work. The example during the State of the Union of converting a completion-based async operation into an async / await one was particularly interesting but the new @Observable macro is the one that will tidy up a huge swathe of my recent SwiftUI code.</p></li><li><p>SwiftUI Animations - Speaking of things that are &#8220;magic&#8221;, SwiftUI was always very good at magically animating things&#8230; until it wasn&#8217;t. Too often did something animate not quite the right way. With the new keyframe animations and other assorted goodies, I&#8217;m hoping I can get some of my SwiftUI interfaces to animate a bit nicer now!</p></li><li><p>Animated SF Symbols - I think these could get overdone very quickly but I can definitely envision a few places where an animated icon would work well.</p></li><li><p>Swift Charts - I really liked the first version of Charts last year but my two wishes were interactivity and pie charts. These have both been added!</p></li><li><p>Swift Data - I&#8217;ve been burned by CoreData too many times and switched to using <a href="https://github.com/realm/realm-swift">Realm</a> over a decade ago. However, this does look really, really nice. I&#8217;m wary of jumping into anything new for what is the absolute foundation of an app but I can definitely see myself testing the waters with this framework later in the year.</p></li><li><p>TipKit - Easily my favourite new addition to the frameworks this year. Clients are always asking to add these little prompts within apps so being able to do it at the system level will be a huge help. I also notice that Apple are using this themselves throughout iOS 17 such as showing you how to undo or edit in Messages.</p></li><li><p>StoreKit Merchandise Views - I&#8217;m very impressed with the views Apple have created to let you easil y create paywalls within your app. It&#8217;s almost like they&#8217;re looking for a way to keep developers using in-app payments &#129300;</p></li></ul><h2>Hardware</h2><ul><li><p><strong><a href="https://www.apple.com/macbook-air-13-and-15-m2/">15&#8221; MacBook Air</a></strong> - I used to use an M1 Max 16&#8221; MacBook Pro but the thickness of it (and some issues with fans) meant I swapped it in for an M2 13&#8221; MacBook Air and a bunch of store credit which may or may not have disappeared on Apple Watch bands &#129762; This machine seems even better; a larger screen without the added thickness and frankly unnecessary power of the Pro. I can imagine I&#8217;ll be getting one of these in a later generation.</p></li><li><p><strong><a href="https://www.apple.com/mac-studio/">Mac Studio 2nd Gen</a></strong> - I&#8217;ve been using the M1 Ultra version of the Mac Studio since it launched and it is far and away the best computer I&#8217;ve ever owned. This is an incredibly solid upgrade with the same pricing. What&#8217;s not to like?</p></li><li><p><strong><a href="https://www.apple.com/mac-pro/">Apple Silicon Mac Pro</a></strong> - It finally appears and uses the same M2 Ultra as the Mac Studio. The only difference is that it costs $3k more than the Studio because it has a huge case and the power to add PCI-E expansion cards for very pro workflows. As it&#8217;s Apple Silicon, the RAM and SSD are set into the chip so cannot be changed after the fact. If you&#8217;re the target market for this device then you&#8217;ll know it, but the Mac Studio is likely the replacement for most Intel Mac Pro users. I think I&#8217;d rather have two Vision Pro&#8217;s &#128521;</p></li></ul><div><hr></div><p>That&#8217;s pretty much it for WWDC so far. I expect there may be a few more tidbits as the final sessions are released but for now all eyes are, quite literally, on Vision Pro.</p><h1>The Spatial Audio Database</h1><p>As many of you will already know, I&#8217;ve been running my own database of Apple&#8217;s Spatial Audio tracks in order to power the <a href="https://dodoapps.io/music-library-tracker/">Music Library Tracker app</a> and my <a href="https://bendodson.com/projects/spatial-audio-finder/">Spatial Audio Finder website</a>. Up until recently, I had also been posting any new tracks that were upgraded to Spatial Audio on the <a href="https://twitter.com/NewSpatialAudio">@NewSpatialAudio Twitter account</a>.</p><p>That was until 19th May when my API key was revoked just as I went on holiday &#129318;&#127995;&#8205;&#9794; The reason for this revocation was &#8220;violating Twitter rules and policies&#8221; but no concrete reason has been given and I can&#8217;t see anything in there that I&#8217;m falling foul of nor an easy way to appeal. This is not a huge surprise though; since Elon Musk took over, Twitter have been aggressively trying to get developers to pay for API access which is something I&#8217;m not prepared to do as it would likely cost 100s of dollars per month.</p><p>In the back of my mind, I had long been planning for this eventuality but not so much that I&#8217;d actually written any code before the final tweet was sent. My plan was always to have a website where you could view the latest upgraded tracks but also provide a way for developers and pro users to access that data.</p><p>This is now a reality with the launch of <a href="https://spatialaudiodb.com">The Spatial Audio Database</a> accessible at <a href="https://spatialaudiodb.com">spatialaudiodb.com</a>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1fX9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbea22dd-1b52-4674-9473-c5313efdb33d_2082x2698.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1fX9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbea22dd-1b52-4674-9473-c5313efdb33d_2082x2698.png 424w, https://substackcdn.com/image/fetch/$s_!1fX9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbea22dd-1b52-4674-9473-c5313efdb33d_2082x2698.png 848w, https://substackcdn.com/image/fetch/$s_!1fX9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbea22dd-1b52-4674-9473-c5313efdb33d_2082x2698.png 1272w, https://substackcdn.com/image/fetch/$s_!1fX9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbea22dd-1b52-4674-9473-c5313efdb33d_2082x2698.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1fX9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbea22dd-1b52-4674-9473-c5313efdb33d_2082x2698.png" width="1456" height="1887" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bbea22dd-1b52-4674-9473-c5313efdb33d_2082x2698.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1887,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:856158,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1fX9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbea22dd-1b52-4674-9473-c5313efdb33d_2082x2698.png 424w, https://substackcdn.com/image/fetch/$s_!1fX9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbea22dd-1b52-4674-9473-c5313efdb33d_2082x2698.png 848w, https://substackcdn.com/image/fetch/$s_!1fX9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbea22dd-1b52-4674-9473-c5313efdb33d_2082x2698.png 1272w, https://substackcdn.com/image/fetch/$s_!1fX9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbea22dd-1b52-4674-9473-c5313efdb33d_2082x2698.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>On the homepage you can view the latest tracks that have been updated and filter them by either Dolby Atmos or Dolby Audio. Each track can be clicked to take you directly to Apple Music.</p><p>On the exports page, you can find RSS feeds for all Spatial Audio, just Dolby Atmos, and just Dolby Audio. These feeds work in real time and will give you the artwork, album title, artist name, and then a list of tracks that have been upgraded.</p><p>Going further, there are also daily CSV exports which are generated each morning to detail every single track that was upgraded on that day.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jo4t!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad65a1-54e0-4939-aa82-cc27f2ea1cdd_2088x2170.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jo4t!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad65a1-54e0-4939-aa82-cc27f2ea1cdd_2088x2170.png 424w, https://substackcdn.com/image/fetch/$s_!jo4t!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad65a1-54e0-4939-aa82-cc27f2ea1cdd_2088x2170.png 848w, https://substackcdn.com/image/fetch/$s_!jo4t!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad65a1-54e0-4939-aa82-cc27f2ea1cdd_2088x2170.png 1272w, https://substackcdn.com/image/fetch/$s_!jo4t!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad65a1-54e0-4939-aa82-cc27f2ea1cdd_2088x2170.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jo4t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad65a1-54e0-4939-aa82-cc27f2ea1cdd_2088x2170.png" width="1456" height="1513" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/80ad65a1-54e0-4939-aa82-cc27f2ea1cdd_2088x2170.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1513,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:424522,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jo4t!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad65a1-54e0-4939-aa82-cc27f2ea1cdd_2088x2170.png 424w, https://substackcdn.com/image/fetch/$s_!jo4t!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad65a1-54e0-4939-aa82-cc27f2ea1cdd_2088x2170.png 848w, https://substackcdn.com/image/fetch/$s_!jo4t!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad65a1-54e0-4939-aa82-cc27f2ea1cdd_2088x2170.png 1272w, https://substackcdn.com/image/fetch/$s_!jo4t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad65a1-54e0-4939-aa82-cc27f2ea1cdd_2088x2170.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Whilst the database has over 10 million tracks in it and has been built up over the past year, I wasn&#8217;t storing the date in which I noticed tracks had been upgraded. That information is stored now but that&#8217;s why exports only start from 1st June 2023.</p><p>In the future I&#8217;ll be migrating the existing <a href="https://bendodson.com/projects/spatial-audio-finder/">Spatial Audio Finder</a> to this website and adding support to search by album and track as well as by artist giving you the ability to search and browse the entire data set in a way that just isn&#8217;t possible on Apple Music.</p><p>Hopefully those of you that found the <a href="https://twitter.com/NewSpatialAudio">@NewSpatialAudio Twitter account</a> useful for surfacing upgraded tracks will like this new website and find it an improvement! If you have any feedback or suggestions, please <a href="mailto:ben@bendodson.com">get in touch</a>.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://spatialaudiodb.com&quot;,&quot;text&quot;:&quot;The Spatial Audio Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://spatialaudiodb.com"><span>The Spatial Audio Database</span></a></p><p></p><h1>The Lossless Audio Database</h1><p>In the last issue I mentioned that Lossless Audio support was going to be coming to the next release of <a href="https://dodoapps.io/music-library-tracker/">Music Library Tracker</a>. To do that, I&#8217;d obviously need a database containing all of that information so&#8230; all that stuff I just mentioned for The Spatial Audio Database? I did it for Lossless Audio as well!</p><p>You get the same list of recent tracks, RSS feeds, and CSV exports but this time for both Hi-Res Lossless and Lossless music. You can check it out at <a href="https://losslessaudiodb.com/">losslessaudiodb.com</a> - enjoy!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://losslessaudiodb.com/&quot;,&quot;text&quot;:&quot;The Lossless Audio Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://losslessaudiodb.com/"><span>The Lossless Audio Database</span></a></p><p></p><h1>ChatGPT as an Intern Developer</h1><p>Despite being a bit of a no-show at WWDC<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a>, it&#8217;s clear that the explosion of LLMs (Large Language Models) such as ChatGPT are already proving to be a foundational shift in how many people work.</p><p>I&#8217;ve been enjoying the writing of Ethan Mollick at <a href="https://www.oneusefulthing.org/">One Useful Thing</a> recently and in particular his article about &#8220;<a href="https://www.oneusefulthing.org/p/on-boarding-your-ai-intern">On-boarding your AI Intern</a>&#8221;:</p><blockquote><p>In fact, perhaps one of the most interesting aspects of our current AI moment is that several billion people just got free interns. They are weird, somewhat alien interns that work infinitely fast and sometimes lie to make you happy, but interns nonetheless.</p><p>So, how can you figure out how to best use your intern? Just like any new worker, you are going to have to learn its strengths and weaknesses; you are going to have to learn to train and work with it; and you are going to have to get a sense of where it is useful and where it is just annoying.</p></blockquote><p>The idea that these new AI chat bots can be seen as fallible interns rather than all-knowing gods that are going to destroy the world certainly seems more grounded in reality to me. To that end, I&#8217;ve been playing around with <a href="https://openai.com/blog/chatgpt-plus">ChatGPT Plus</a> and ended up using it to write some of the exporting code for The Spatial Audio Database.</p><p>In the past, when looking to add a feature to a project I&#8217;d usually go back through my past work and copy and paste from there. If it was something I hadn&#8217;t worked on before, I&#8217;d look on Stack Overflow or find some articles or documentation to get an understanding.</p><p>With an AI intern, things become a bit easier as you can throw the problem at them in a more specific way. Rather than searching for &#8220;php generate rss feed&#8221;, I was able to give an example of my data array and ask it to create the code I needed using some specific variables:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!k5vj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F757703c0-8a1c-4fa1-a94c-57d41da2aecc_1722x3922.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!k5vj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F757703c0-8a1c-4fa1-a94c-57d41da2aecc_1722x3922.png 424w, https://substackcdn.com/image/fetch/$s_!k5vj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F757703c0-8a1c-4fa1-a94c-57d41da2aecc_1722x3922.png 848w, https://substackcdn.com/image/fetch/$s_!k5vj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F757703c0-8a1c-4fa1-a94c-57d41da2aecc_1722x3922.png 1272w, https://substackcdn.com/image/fetch/$s_!k5vj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F757703c0-8a1c-4fa1-a94c-57d41da2aecc_1722x3922.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!k5vj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F757703c0-8a1c-4fa1-a94c-57d41da2aecc_1722x3922.png" width="1456" height="3316" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/757703c0-8a1c-4fa1-a94c-57d41da2aecc_1722x3922.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3316,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:865493,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!k5vj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F757703c0-8a1c-4fa1-a94c-57d41da2aecc_1722x3922.png 424w, https://substackcdn.com/image/fetch/$s_!k5vj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F757703c0-8a1c-4fa1-a94c-57d41da2aecc_1722x3922.png 848w, https://substackcdn.com/image/fetch/$s_!k5vj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F757703c0-8a1c-4fa1-a94c-57d41da2aecc_1722x3922.png 1272w, https://substackcdn.com/image/fetch/$s_!k5vj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F757703c0-8a1c-4fa1-a94c-57d41da2aecc_1722x3922.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Not only does it return well formed code that compiled (complete with in-line comments) it also gave me a set of notes to explain how it works. It also reminded me to change the title, link, and description of the feed as it had given me some random examples inferred from my content.</p><p>This worked brilliantly as I was able to get a working RSS feed running in around 20 seconds. Whilst I have some RSS feed code already on my own personal website, that was written in a different language and so it would have taken me a few minutes to restructure my data accordingly. This isn&#8217;t something that is saving me days of time, but it is still saving me time.</p><p>When I put in my actual data, I received some PHP warnings which I gave back to ChatGPT:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HBHA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b7baafd-4460-42c2-bd6a-0b377ee8d439_1478x2668.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HBHA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b7baafd-4460-42c2-bd6a-0b377ee8d439_1478x2668.png 424w, https://substackcdn.com/image/fetch/$s_!HBHA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b7baafd-4460-42c2-bd6a-0b377ee8d439_1478x2668.png 848w, https://substackcdn.com/image/fetch/$s_!HBHA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b7baafd-4460-42c2-bd6a-0b377ee8d439_1478x2668.png 1272w, https://substackcdn.com/image/fetch/$s_!HBHA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b7baafd-4460-42c2-bd6a-0b377ee8d439_1478x2668.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HBHA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b7baafd-4460-42c2-bd6a-0b377ee8d439_1478x2668.png" width="1456" height="2628" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9b7baafd-4460-42c2-bd6a-0b377ee8d439_1478x2668.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2628,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:522228,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HBHA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b7baafd-4460-42c2-bd6a-0b377ee8d439_1478x2668.png 424w, https://substackcdn.com/image/fetch/$s_!HBHA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b7baafd-4460-42c2-bd6a-0b377ee8d439_1478x2668.png 848w, https://substackcdn.com/image/fetch/$s_!HBHA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b7baafd-4460-42c2-bd6a-0b377ee8d439_1478x2668.png 1272w, https://substackcdn.com/image/fetch/$s_!HBHA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b7baafd-4460-42c2-bd6a-0b377ee8d439_1478x2668.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Notice how I formed the request; this isn&#8217;t a Google search command but instead a natural question I might have asked a colleague. Again, the &#8220;intern&#8221; provides an answer, an explanation, and some updated code.</p><p>I performed a few more follow up questions, sometimes days later, asking how to add a build time, last published date, and creator information and each time the results came back exactly as I needed them. I then moved onto performing the CSV export and ChatGPT was able to provide code for me to use based on specific data sets I&#8217;d provided.</p><p>This kind of chatbot relationship is excellent for independent developers. When you have a question about some specific area that you&#8217;ve maybe not worked with for a while, it can give you the samples you need. I particularly like the way it can act as a data transformer as you can give it the exact variables to use rather than having to adjust the code to match your project. Coding is the perfect area for a chatbot to work in as often there can only be one right answer; the code compiles, or it doesn&#8217;t.</p><p>Such a system is not perfect though. An LLM is just a set of glorified if statements and it will tell you things with confidence even if they&#8217;re not true. Imagining it as an intern frames in your mind that the answers may not be correct and do actually need checking. That said, it can work really well for doing initial research or simple coding tasks that you don&#8217;t want to do yourself.</p><p>I&#8217;m not comfortable with having ChatGPT write articles for me or to generate high swathes of code but these examples for adding data exports to The Spatial Audio Database have made me realise that I should get some practice with using these new tools. They are undoubtedly the future.</p><h1>Recommendations</h1><h4>Video Games</h4><ul><li><p><em><strong><a href="https://www.zelda.com/tears-of-the-kingdom/">The Legend of Zelda: Tears of the Kingdom</a></strong></em> - It&#8217;s good. It&#8217;s very, <em>very</em> good. I&#8217;ve played around 60 hours since it launched and I&#8217;ve barely scratched the surface in what is a huge game filled with things to do. When I say &#8220;huge&#8221;, it&#8217;s around 2-3x the size of <em>Breath of the Wild</em>. That it runs on the Switch is genuinely mind blowing, especially as there are no frame rate or graphical issues. It doesn&#8217;t even feel like it&#8217;s 30fps (and I consider myself very sensitive to frame rates). Undoubtedly one of the best games of the past 10-15 years. Go and get it played.</p></li></ul><h4>TV</h4><ul><li><p><em><strong><a href="https://www.justwatch.com/uk/tv-series/succession">Succession</a></strong></em> - I decided to start watching this just as the show finished &#128514; I powered through season 1 in a couple of days and fully intend to do the same with the remaining three seasons. Excellent writing with excellent actors. Kieran Culkin gets a special mention from me for having the best lines (introducing his new girlfriend to his mother towards the end of the series is particularly memorable).</p></li></ul><h1>Roadmap</h1><p>The roadmap is my way of committing to what I&#8217;m going to do before the next issue:</p><h4>11th May - 7th June</h4><ul><li><p>Complete the Lossless Audio integration in <em>Music Library Tracker </em>v3 <em>&#10060;</em></p></li><li><p>Get the BoardGameGeek collection sync complete in <em>Board Game Lists &#10060;</em></p></li><li><p>Build something with the new iOS 17 SDK (or RealityOS &#128064;) <em>&#10060;</em></p></li></ul><p>Oof, not a good month in term of achieving my goals but the Twitter API issues meant the Spatial Audio and Lossless Audio databases became a higher priority (and are still useful for <em>Music Library Tracker v3</em>) whilst iOS 17 didn&#8217;t have anything to entice me.</p><p>For the next issue, I feel like I have some breathing room again to get back to focussing on my apps so let&#8217;s try this list again!</p><h4>8th June - 5th July (Issue #11)</h4><ul><li><p>Complete the Lossless Audio integration in <em>Music Library Tracker </em>v3</p></li><li><p>Get the BoardGameGeek collection sync complete in <em>Board Game Lists</em></p></li></ul><p>That wraps it up for this issue. I hope you found something of interest and that you&#8217;ll be able to recommend the newsletter to your friends, family, and colleagues. You can always comment on this issue or email me directly via <a href="mailto:ben@bendodson.com">ben@bendodson.com</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/subscribe?"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/10-the-first-spatial-computer/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/10-the-first-spatial-computer/comments"><span>Leave a comment</span></a></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>I love that they&#8217;ve put a full 3D render of The Steve Jobs Theatre into Keynote so you can practice your presentations on <em>that</em> stage. The seats look lovely &#128525;</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>I used this feature on a HTC Vive to find my glass of wine&#8230; unfortunately I forgot I had a massive headset on and smashed it into that rather than into my mouth &#129318;&#127995;&#8205;&#9794;</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>The iPad was rumoured to be $1000 but then launched at $499. Steve Jobs made a big thing about it during that Keynote.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>I haven&#8217;t even talked about the fact you can control this thing with your hands and eyes!</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>Sometimes Sherlocking will kill your entire business (i.e. Sticker Drop only does one thing and that&#8217;s now done by the system) but for more complex apps it can be a boon as users will try out the system version but then want to buy an app which adds more features. Task apps didn&#8217;t disappear after Reminders came out!</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>I just want Donald Duck and Goofy to be added to the Mickey and Minnie faces &#128591;&#127995;</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p>Apple is avoiding any big changes to Siri or a &#8220;co-pilot&#8221; for Xcode but there are still some LLMs in iOS 17. For example, the new autocorrect feature? That&#8217;s an LLM.</p></div></div>]]></content:encoded></item><item><title><![CDATA[#9: The Waiting Place]]></title><description><![CDATA[WWDC is calling. Please hold.]]></description><link>https://www.thedododeveloper.com/p/9-the-waiting-place</link><guid isPermaLink="false">https://www.thedododeveloper.com/p/9-the-waiting-place</guid><dc:creator><![CDATA[Ben Dodson]]></dc:creator><pubDate>Wed, 10 May 2023 13:45:51 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/7e18d7af-cb54-4797-b0da-e0e1f33d5597_1138x792.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dear Reader,</p><p>Let me begin by giving a big thank you to all of you who messaged me following <a href="https://www.thedododeveloper.com/p/8-healthy-living">last month&#8217;s newsletter</a> with your well wishes and encouragements. After losing a stone and significantly improving my fitness level, I&#8217;ve had another blood test which suggests I could already be in remission for diabetes. I need to have the same levels in three more tests over the next 9 months but it&#8217;s looking positive (or should that be negative?) so far.</p><p>Health aside, I&#8217;ve been struggling with motivation this past month. This can be attributed partly to a recent overload of client work but I think it&#8217;s mostly due to waiting. Whilst many people enjoy having something to look forward to, I absolutely hate waiting for anything. This is likely a symptom of the modern age in which instant gratification is around every corner<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> but I find myself in a holding pattern when I have something I&#8217;m waiting on.</p><p>I&#8217;m reminded of this passage from Dr Seuss&#8217; <em>Oh, the Places You&#8217;ll Go:</em></p><blockquote><p>You can get so confused<br>that you'll start in to race<br>down long wiggled roads at a break-necking pace<br>and grind on for miles cross weirdish wild space,<br>headed, I fear, toward a most useless place.<br>The Waiting Place...<br><br>...for people just waiting.<br>Waiting for a train to go<br>or a bus to come, or a plane to go<br>or the mail to come, or the rain to go<br>or the phone to ring, or the snow to snow<br>or the waiting around for a Yes or No<br>or waiting for their hair to grow.<br>Everyone is just waiting.<br><br>Waiting for the fish to bite<br>or waiting for the wind to fly a kite<br>or waiting around for Friday night<br>or waiting, perhaps, for their Uncle Jake<br>or a pot to boil, or a Better Break<br>or a string of pearls, or a pair of pants<br>or a wig with curls, or Another Chance.<br>Everyone is just waiting.</p></blockquote><p>So what am I waiting for? This week it&#8217;s the release of <em>The Legend of Zelda: Tear&#8217;s of the Kingdom</em>, a game I have only got more excited about as time has gone by. As I type this I&#8217;m impatiently waiting for the review embargo to drop &#128514;. The week after, I&#8217;m away on a Disney Cruise (where I naively expect to sit on a sun lounger and play <em>Tear&#8217;s of the Kingdom</em>) and then a couple of weeks after that is WWDC 2023 where it&#8217;s almost certain Apple are going to show off their new VR/AR headset along with the usual updates to all of their platforms.</p><p>This last one is the real kicker as WWDC is the time everything gets upended. In the indie development space, the launch of a new version of iOS is the time that getting a coveted featured spot from Apple or the wider blogosphere is more likely <em>if</em> you can add some new feature to your app related to the iOS update. The problem with this is that you typically get about 2 months to come up with this idea and incorporate it into your app whilst dealing with the multitude of bugs in the beta software and the lack of concrete documentation. It can be a stressful time! This is further compounded by the fact that iOS 17 may end up breaking something in existing apps and that I don&#8217;t have to fix just my own but also my clients (who may also want my time to add new features to their apps before the launch date in September).</p><p>So, whilst I&#8217;m very much looking forward to seeing all the fancy new tech and announcements I&#8217;m also dreading the mad summer rush that will inevitably ensue and find my motivation a little low when I have a game and a holiday to enjoy in the near future followed by a week that could completely upend my summer.</p><p>The next issue will be on the 7th June, smack bang in the middle of WWDC so we&#8217;ll find out then how busy the summer is going to be!</p><p><strong>&#8212; Ben</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/9-the-waiting-place/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/9-the-waiting-place/comments"><span>Leave a comment</span></a></p><h1>Contents</h1><ul><li><p>Hello, Audio Variants! (<em>Music Library Tracker</em> v3)</p></li><li><p>Debouncing with Search &amp; Add screens</p></li><li><p>Apple TV to iPhone communication</p></li><li><p>Recommendations</p></li><li><p>Roadmap</p></li></ul><h1>Hello, Audio Variants!</h1><p>I&#8217;ve been slowly plugging away at Music Library Tracker v2.1 but am not yet at a stage where I have a beta version, let alone a release version, ready to share. That&#8217;s partly due to lethargy and feature creep, but also because I&#8217;ve now decided<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> this will be v3.0 as I&#8217;m planning to redesign some of the core navigational concepts and add a few big features &#127881;.</p><p>For today, I thought I would detail what is going to be in this update and what issues I&#8217;ve encountered in my thinking so far.</p><p></p><h4>Audio Variants</h4><p>The headline feature is that the app won&#8217;t just tell you when tracks are upgraded to Spatial Audio but will also be able to tell you about every &#8220;<a href="https://developer.apple.com/documentation/musickit/audiovariant?changes=_3_5">Audio Variant</a>&#8221; that Apple provides<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> including:</p><ul><li><p>Dolby Atmos (&#8220;an immersive audio experience that surrounds you with sound from all sides, including above&#8221;)</p></li><li><p>Dolby Audio (&#8220;a surround sound format that includes Dolby 5.1 and 7.1&#8221;)</p></li><li><p>Hi-Res Lossless Audio (&#8220;uses Apple Lossless Audio Codec (ALAC) for bit-for-bit accuracy up to 24-bit/192 kHz&#8221;)</p></li><li><p>Lossless Audio (&#8220;uses Apple Lossless Audio Codec (ALAC) for bit-for-bit accuracy up to 24-bit/48 kHz&#8221;)</p></li></ul><p>The actual mechanics of how this works are effectively the same as how Spatial Audio is detected. I have a database that collects Apple Music tracks (both from playlists and from the libraries of users who opt-in) and the albums are then checked periodically against the Apple Music API to fetch their metadata including these audio variants. In fact, I&#8217;ve been storing the list of variants as tags on each track since I started the database over a year ago so if you&#8217;ve used <em>Music Library Tracker</em> already then the new version will  be able to tell you instantly which tracks are Lossless or Hi-Res Lossless.</p><p>The problem with this feature isn&#8217;t the technical workings but how I present it within the UI of the app. I can narrow this down to two problems: naming and settings.</p><p>At present, the app has two tabs: Activity and Spatial Audio. My initial feeling was that I should replace the Spatial Audio tab with one named &#8220;Audio Variants&#8221; which would then list the 4 types of audio above (and possibly a 5th one for Spatial Audio which is basically a grouping of both Dolby Atmos or Dolby Audio; that&#8217;s how the app classifies it currently) and link you to a list showing all of the audio you have that matches that particular audio format. My problem with this approach is that &#8220;Audio Variants&#8221; is a boring name and not descriptive at all. It&#8217;s the name Apple uses internally but that&#8217;s not necessarily a compelling reason to use it as it certainly isn&#8217;t used in anything user facing.</p><p>I&#8217;m having a mental block thinking of anything else suitable to name this tab. Advanced Audio? Audio Formats? Spatial &amp; Friends? &#128514;</p><p>If you&#8217;ve got an idea, then please do leave a comment!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/9-the-waiting-place/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.thedododeveloper.com/p/9-the-waiting-place/comments"><span>Leave a comment</span></a></p><p>Another possible option is to simply add an additional tab named &#8220;Lossless Audio&#8221;<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a> and provide some sort of filter within that so you can easily limit by either Hi-Res Lossless, plain old Lossless, or both together. This kind of filter could be extended to the Spatial Audio tab so users can filter between Dolby Atmos, Dolby Audio, or both as well.</p><p>After typing this out, I think I&#8217;m leaning more to the second option as I feel that three tabs looks better than two and I like the idea of them being a simple collection of that type of music but with filters for those that want just Dolby Atmos or Hi-Res Lossless.</p><p>(As a brief aside there is a common concept in software development known as &#8220;<a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging">Rubber Duck debugging</a>&#8221; whereby you explain your problem to an inanimate rubber duck as articulating your problem can sometimes help you solve it. That seems to work for writing newsletters as well!)</p><p>The second problem I&#8217;ve been pondering on for some time is how to sort out the settings page and onboarding with these extra audio variants. At present, the settings page has a toggle to enable anything to do with Spatial Audio which, when enabled, reveals push notification settings and an option to create a Spatial Audio playlist; when this option is enabled, you then get the option to re-generate that playlist. I don&#8217;t really want to duplicate this 5 times so instead it seems like this needs to be nested within a single option (which brings us back to the &#8220;Audio Variants is a naff name&#8221; problem).</p><p>Again, Rubber Duck debugging may have helped as I now, as I type this, think that this too could be broken down into just two options of Spatial Audio and Lossless Audio. Either one would take you to a more detailed settings page where you could choose to:</p><ol><li><p>Enable the feature (i.e. Lossless Audio [off / on])</p></li><li><p>If enabled, it would then let you decide what qualifies (i.e. it would list &#8220;Hi-Res Lossless&#8221; and &#8220;Lossless&#8221; both with ticks next to them but you could untick &#8220;Lossless&#8221; to only care about &#8220;Hi-Res Lossless&#8221;)</p></li><li><p>The playlist and notifications would then use those settings</p></li></ol><p>With this, it would be possible to mirror what the app already does with Spatial Audio thus making upgrading easier (in that it&#8217;s both Dolby Atmos and Dolby Audio which would be the default in this version but you could choose to disable Dolby Audio if you wanted). </p><p>The only problem I see with this is if somebody wanted to have distinct playlists for each audio variant i.e. they want a playlist for Dolby Atmos and a separate playlist for Dolby Audio. I&#8217;m not <em>too</em> concerned about this as it isn&#8217;t an option currently so I don&#8217;t feel duty bound to include that distinction when dealing with Hi-Res Lossless and Lossless; my assumption is that most people only care about everything or only the best quality ones (i.e. they either really care about audio and want just Hi-Res Lossless or they don&#8217;t care and want both types lumped together).</p><p>I think I may have just solved this by explaining it here but if you have strong feelings on either approach I&#8217;d love to hear your feedback!</p><p></p><h4>Search and Redesign</h4><p>The key thing that customers have been asking me for is a search system so you can find a particular track, album, or artist and view the changes. This should be relatively easy to put into place but it also leads me to want to redesign the way in which metadata is displayed. At the moment it&#8217;s just text where a heading gets highlighted if something changed. It&#8217;s been this way for over 6 years.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ddqw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e25abb-b081-4c93-9fae-fb1a0fd90453_1450x2936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ddqw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e25abb-b081-4c93-9fae-fb1a0fd90453_1450x2936.png 424w, https://substackcdn.com/image/fetch/$s_!ddqw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e25abb-b081-4c93-9fae-fb1a0fd90453_1450x2936.png 848w, https://substackcdn.com/image/fetch/$s_!ddqw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e25abb-b081-4c93-9fae-fb1a0fd90453_1450x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!ddqw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e25abb-b081-4c93-9fae-fb1a0fd90453_1450x2936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ddqw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e25abb-b081-4c93-9fae-fb1a0fd90453_1450x2936.png" width="294" height="595.2993103448276" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a2e25abb-b081-4c93-9fae-fb1a0fd90453_1450x2936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2936,&quot;width&quot;:1450,&quot;resizeWidth&quot;:294,&quot;bytes&quot;:1314791,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ddqw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e25abb-b081-4c93-9fae-fb1a0fd90453_1450x2936.png 424w, https://substackcdn.com/image/fetch/$s_!ddqw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e25abb-b081-4c93-9fae-fb1a0fd90453_1450x2936.png 848w, https://substackcdn.com/image/fetch/$s_!ddqw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e25abb-b081-4c93-9fae-fb1a0fd90453_1450x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!ddqw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e25abb-b081-4c93-9fae-fb1a0fd90453_1450x2936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It works, but I&#8217;d prefer something a bit more modern. This will most likely be in the form of small tiles for each item complete with an icon. Rather than &#8220;Spatial Audio: No&#8221;, I&#8217;d have a tile for Audio Variants with each one shown as a tag. There would also be a direct link to the track, album, or artist on Apple Music and options to play the track.</p><p>Underneath these tiles would then be a historical list of metadata changes.</p><p>This is a profound difference as the app was designed as Activity &gt; Date &gt; Tracks &gt; Metadata. The key thing was the change in metadata that happened on each day so the app was built around that concept. However, I think a better design is to be more akin to a music player where you drill down to tracks through other groupings and then see a single unified page for that track which can then list any changes. After all, most people are only interested that something changed and what that last change was. The new flow could therefore be thought of as Activity &gt; Date &gt; [Albums | Artists &gt;] Tracks &gt; Track.</p><p>With this in place, every part of the app can link to the same page regardless of whether they came from Activity, Spatial Audio, Lossless Audio, or Search.</p><p></p><h4>Redesigning Spatial Audio</h4><p>At the moment the Spatial Audio tab lists all of the tracks grouped by the date in which they were upgraded. Whilst you can sort the tracks within their dates by track title, artist name, album name, or album artist, the list is still only displaying tracks.</p><p>This page will be redesigned to add several new features:</p><ul><li><p>Based on the outcome of the discussion with myself a few paragraphs up, there will be a filter to switch between Dolby Atmos, Dolby Audio, or All Tracks.</p></li><li><p>There will be a search bar so you can quickly filter the list</p></li><li><p>Rather than breaking down the list by date, it will just be a list of all of the tracks that are upgraded with no sectioning (the date a track is upgraded is already handled by notifications and the Activity tab so does not need to be replicated here)</p></li><li><p>The items in the list will be customisable between showing tracks, albums, or artists making it much faster to browse. Tapping an album or artist would then list the tracks. Tapping a track would take you to the new track view showing the last snapshot of metadata with the redesigned tile interface I mentioned earlier rather than just immediately playing the song in the background (which has confused a few users).</p></li></ul><p>All of these changes would also apply to the new Lossless Audio tab.</p><p></p><h4>Other changes</h4><ul><li><p><strong>SwiftUI</strong> - As I&#8217;m redesigning most of the pages in the app, it&#8217;s now time to just migrate everything to SwiftUI. I&#8217;m already using it for the onboarding and settings pages but I&#8217;m comfortable enough with it now that I think it&#8217;ll be better for the whole app.</p></li><li><p><strong>Navigate between days</strong> - If you are viewing a particular date then there will be arrows and shortcuts to let you jump forwards and backwards.</p></li><li><p><strong>Improved sorting</strong> - Handling the removal of common prefixes like &#8220;the&#8221;, &#8220;an&#8221;, &#8220;a&#8221;, etc, and using disc / track numbers to arrange albums.</p></li><li><p><strong>Exports and Shortcuts </strong>- I&#8217;d like to improve the shortcuts support as there are a few issues with it and the Shortcuts extensions have come a long way since I wrote the initial integration. I&#8217;d also like to add CSV exports so you can, for example, easily pull a report of which tracks have been deleted.</p></li></ul><p>Phew, I think that&#8217;s enough for one update and will hopefully finish the modern design transition that started with v2. I was originally thinking this update would be done by now but as I&#8217;ve increased the scope somewhat I think August for launch is more optimistic. The aim would certainly be to have this done and dusted by early July to then give a few weeks time for reviewers to use the app. This will also give me some breathing room for iOS 17 and adding any new features that may arise from that in a v3.1 update in September.</p><h1>Debouncing with Search &amp; Add screens</h1><p>One of my tasks for this month was to complete the main list feature of my upcoming app, <em>Board Game Lists</em>. The basic idea is that people will be able to create lists of the board games they own with the intention of being able to add notes to them and provide a simple export to share with friends when choosing what to play. For example, I might want to curate a list of games suitable for 4 players that I think my neighbours would enjoy.</p><p>Whilst the app will have a bit more nuance than that, a good first step is what I&#8217;ve come to term &#8220;Search &amp; Add&#8221;; a system which lets you add an item to a list by performing a text search.</p><p>I&#8217;ve used this previously in my personal <em>Game Track</em> app which lets me log time spent playing video games:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NaRr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4b3244a-465c-465c-bbe1-0b3e37c64cab_4137x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NaRr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4b3244a-465c-465c-bbe1-0b3e37c64cab_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!NaRr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4b3244a-465c-465c-bbe1-0b3e37c64cab_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!NaRr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4b3244a-465c-465c-bbe1-0b3e37c64cab_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!NaRr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4b3244a-465c-465c-bbe1-0b3e37c64cab_4137x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NaRr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4b3244a-465c-465c-bbe1-0b3e37c64cab_4137x2716.png" width="1456" height="956" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f4b3244a-465c-465c-bbe1-0b3e37c64cab_4137x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:956,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1708795,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NaRr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4b3244a-465c-465c-bbe1-0b3e37c64cab_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!NaRr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4b3244a-465c-465c-bbe1-0b3e37c64cab_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!NaRr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4b3244a-465c-465c-bbe1-0b3e37c64cab_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!NaRr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4b3244a-465c-465c-bbe1-0b3e37c64cab_4137x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Adding a game to a list via Search &amp; Add</figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HYNX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F882ca461-8e99-4425-8b48-15c101e961ce_4137x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HYNX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F882ca461-8e99-4425-8b48-15c101e961ce_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!HYNX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F882ca461-8e99-4425-8b48-15c101e961ce_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!HYNX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F882ca461-8e99-4425-8b48-15c101e961ce_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!HYNX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F882ca461-8e99-4425-8b48-15c101e961ce_4137x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HYNX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F882ca461-8e99-4425-8b48-15c101e961ce_4137x2716.png" width="1456" height="956" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/882ca461-8e99-4425-8b48-15c101e961ce_4137x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:956,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4839384,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HYNX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F882ca461-8e99-4425-8b48-15c101e961ce_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!HYNX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F882ca461-8e99-4425-8b48-15c101e961ce_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!HYNX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F882ca461-8e99-4425-8b48-15c101e961ce_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!HYNX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F882ca461-8e99-4425-8b48-15c101e961ce_4137x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Logging game time via Search &amp; Add</figcaption></figure></div><p>The user taps on a search (&#128269;) or plus (&#10133;) icon and is presented with a search screen. They type what they want, press &#8220;search&#8221;, and after a short loading screen the results are returned. The user can then tap a search result to perform the next action be that viewing a details page, adding to a list, create a time log, etc.</p><p>This all seems straightforward enough and is something you&#8217;ve no doubt seen in countless apps. As with most things, however, the bare minimum isn&#8217;t enough in this day and age; one can&#8217;t expect their users to have to press a button to search! No, the search should happen automatically as the user types. This adds a couple of wrinkles depending on how your data is retrieved:</p><ol><li><p>If you&#8217;re fetching data from the network (as in this case) then you don&#8217;t want to send a request after every character is typed as then you&#8217;ll potentially end up with 10-20 requests of which only the last one is important. In this case we need to find a way to add a suitable delay to requests to ensure we aren&#8217;t sending too many or continuing with requests which are no longer needed.</p></li><li><p>On the other hand, if you&#8217;re fetching data from a local database, then the search may act more like a filter as it is possible to display results in realtime (i.e. as soon as you type a character you could feasibly show results for that search term). This method has it&#8217;s own problems as you may end up having the interface flicker wildly if you&#8217;re a fast typer and the search results become closer to what you&#8217;re actually typing (i.e. if I had a local dataset of games, the chance of &#8220;G&#8221; matching the game <em>Ghostwire: Tokyo</em> is minimal&#8230; it&#8217;ll probably start to match after the 5th or 6th characters). In this scenario you&#8217;ll likely want to slow down the filtering so it looks like the content is loading rather than being instantly available.</p></li></ol><p>It is incredibly easy to get a search interface wrong. One of my pet peeves is when network requests are fired indiscriminately and then content is loaded regardless of whether further characters have been typed (i.e. seeing search results for &#8220;Gh&#8221; but the search bar is showing &#8220;Ghostwire&#8221; then a few seconds later the newer results pop in). This is compounded by search results that return images as you end up with two pieces of network latency; the search itself and then images beginning to load which then may not be required if the search progresses further.</p><p>So how do we fix all this? The best way is to use something known as <em><a href="https://www.techtarget.com/whatis/definition/debouncing">debouncing</a></em>.</p><blockquote><p>Debouncing is removing unwanted input noise from buttons, switches or other user input. Debouncing prevents extra activations or slow functions from triggering too often.</p></blockquote><p>The process is relatively simple. When somebody types a character, we start a timer which will call our search function. If another character is typed, we cancel the timer and start another one. For example, if we set our timer to be 1 second, then a user typing three characters per second (180cpm) would not see any requests until 1 second after they finished typing their query.</p><p>There are two problems with this: if we make our delay too short then slower typists may end up with requests after every character whereas if we make it too long then there will be an unnecessary delay after the search term has been entered. We can mitigate the latter point slightly by ensuring that if a search button or enter key is pressed that we cancel all timers and send a request immediately but the choice of delay will ultimately boil down to personal preference and your audience. If your app is going to be used by an older (or very young) audience then you&#8217;ll likely want a longer delay than an app used by teenagers.</p><p>It is also important to remember that cancelling the timer is only preventing the next request from firing; it won&#8217;t necessarily cancel any network requests that are already active. For example, if we had our 1 second timer and the user entered an extra character after 1.2 seconds then there wouldn&#8217;t be a timer to cancel as it&#8217;s already fired; instead we need to cancel any network requests.</p><p>This is a lot easier with Swift&#8217;s relatively new await/async concurrency as we can effectively merge our timers and network requests into a single task which, when cancelled, will cancel everything:</p><pre><code>let client = IGDBAPIClient()

private var searchTask: Task&lt;[RemoteGame], Error&gt;?

func fetchSearchResults(for query: String, debounce: Bool) async throws -&gt; [Game] {
    searchTask?.cancel()
    guard !query.isEmpty else {
        return []
    }
    searchTask = Task { () -&gt; [RemoteGame] in
        if debounce {
            try await Task.sleep(nanoseconds: 800_000_000)
        }
        let request = IGDBAPI.Search.post(query: query)
        return try await client.send(request, andDecodeTo: [RemoteGame].self)
    }
    guard let result = await searchTask?.result else { throw APIError.unknown }
    return try result.get().map { $0.convertToGame() }
}</code></pre><p>When our <code>fetchSearchResults()</code> function is called, we first cancel the previous <code>searchTask</code> if there was one. After sanity checking that there is a search term, we then begin a new task. If we are debouncing (i.e. this has been called from typing rather than pressing &#8220;search&#8221;) we add a 0.8 second delay. Next we perform our actual API call and JSON decoding before returning the list of game objects for the UI to render.</p><p>The key thing here is that instead of using timers that we have to cancel independently of network requests, we instead use <code>Task.sleep()</code> to create a delay within the task itself. If the task is cancelled, then everything is cancelled including any network requests or slow-running JSON decoding tasks.</p><p>With this newer concurrency version complete, I added it not only to my <em>Game Track</em> app but also to my <em>Bookmark</em> app that I talked about in <a href="https://www.thedododeveloper.com/i/94296031/bookmark">Issue #3</a>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qo6x!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac0f5d6-60c6-49f1-a80a-4059fe3e31d1_4137x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qo6x!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac0f5d6-60c6-49f1-a80a-4059fe3e31d1_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!qo6x!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac0f5d6-60c6-49f1-a80a-4059fe3e31d1_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!qo6x!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac0f5d6-60c6-49f1-a80a-4059fe3e31d1_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!qo6x!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac0f5d6-60c6-49f1-a80a-4059fe3e31d1_4137x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qo6x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac0f5d6-60c6-49f1-a80a-4059fe3e31d1_4137x2716.png" width="1456" height="956" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4ac0f5d6-60c6-49f1-a80a-4059fe3e31d1_4137x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:956,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2691185,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qo6x!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac0f5d6-60c6-49f1-a80a-4059fe3e31d1_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!qo6x!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac0f5d6-60c6-49f1-a80a-4059fe3e31d1_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!qo6x!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac0f5d6-60c6-49f1-a80a-4059fe3e31d1_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!qo6x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac0f5d6-60c6-49f1-a80a-4059fe3e31d1_4137x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Previously I had been adding books to my personal database manually but it was becoming a bit of a hassle. The process would generally require me to do a Google Image search for the artwork, upload it to my server, then add the title and artist name manually in my database. There aren&#8217;t many APIs for book searches and those that do exist tend to have low quality images (*cough* GoodReads *cough cough*). That&#8217;s when I remembered that I had already <a href="https://bendodson.com/weblog/2013/02/19/itunes-artwork-finder/">solved this problem in 2013</a> with my <a href="https://bendodson.com/projects/itunes-artwork-finder/">iTunes Artwork Finder</a>; it can return details of iBooks (or Apple Books as they have been known since 2019) and my artwork adjustments allow me to fetch the original, uncompressed cover art that the publisher provides.</p><p>The API request is incredibly simple and doesn&#8217;t even require authentication:</p><pre><code>GET https://itunes.apple.com/search?term=grave%2Bexpectations&amp;country=gb&amp;entity=ibook&amp;limit=25</code></pre><p>This returns all the details of the books including artwork, title, author<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>, pricing, genre, descriptions, release date, etc. With this data, I save the URL for the image in my database and then have a CRON job which runs daily to download, resize, and upload the images to my server so I always have a static copy. </p><p>It may be that there will be some books in the future that are not available on this service for which I'll need to revert to my old manual input but it's working well for now. It&#8217;s always nice when something you built over a decade ago makes a new task a 2 minute job &#128517;</p><div><hr></div><p>Anyway, after 1318 words lets get back to the topic at hand which was <em>Board Game Lists</em>. This, again, requires a &#8220;Search &amp; Add&#8221; screen which will perform a search against the <a href="https://boardgamegeek.com/wiki/page/BGG_XML_API&amp;redirectedfrom=XML_API#">BoardGameGeek API</a> and return a list of games; upon selection, the game will be stored in a local Realm database and added to the respective list.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!boe8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1ecc29-7499-43a2-83be-53c480281682_5320x1908.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!boe8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1ecc29-7499-43a2-83be-53c480281682_5320x1908.png 424w, https://substackcdn.com/image/fetch/$s_!boe8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1ecc29-7499-43a2-83be-53c480281682_5320x1908.png 848w, https://substackcdn.com/image/fetch/$s_!boe8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1ecc29-7499-43a2-83be-53c480281682_5320x1908.png 1272w, https://substackcdn.com/image/fetch/$s_!boe8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1ecc29-7499-43a2-83be-53c480281682_5320x1908.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!boe8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1ecc29-7499-43a2-83be-53c480281682_5320x1908.png" width="1456" height="522" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ba1ecc29-7499-43a2-83be-53c480281682_5320x1908.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:522,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3365807,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!boe8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1ecc29-7499-43a2-83be-53c480281682_5320x1908.png 424w, https://substackcdn.com/image/fetch/$s_!boe8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1ecc29-7499-43a2-83be-53c480281682_5320x1908.png 848w, https://substackcdn.com/image/fetch/$s_!boe8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1ecc29-7499-43a2-83be-53c480281682_5320x1908.png 1272w, https://substackcdn.com/image/fetch/$s_!boe8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba1ecc29-7499-43a2-83be-53c480281682_5320x1908.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The code for this is pretty much identical to the previous two apps thanks to using SwiftUI and it&#8217;s one-liner <code>.searchable()</code> modifier to create the search bar along with an <code>.onChange()</code> and <code>.onSubmit(of: .search)</code> to perform our search with and without debouncing respectively. The problem with this app is that <a href="https://boardgamegeek.com/">BoardGameGeek</a> has the best data set of board games but has undoubtedly one of the worst APIs I&#8217;ve ever used.</p><p>Why is it so bad? Well, first of all it only serves XML. That&#8217;s not necessarily a dealbreaker but it does speak to a very old design. Secondly, some API calls won&#8217;t return a result immediately but instead queue one up and return a <code>202 Accepted</code> header requiring you to check again in a few seconds for the actual data &#129318;&#127995;&#8205;&#9794;. Finally, the API will typically require you to make multiple batched calls to get any usable data.</p><p>For example, let&#8217;s take a look at a simple search for board games that match &#8220;Lord of the Rings&#8221;:</p><pre><code>GET: https://boardgamegeek.com/xmlapi/search?search=Lord%20of%20the%20Rings</code></pre><p>Rather than a paginated list of games sorted by popularity, you instead get an alphabetical list of every matching game&#8217;s identifier, title, and year of publication; in this case, that&#8217;s 261 games<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>. You then need to take that and pipe the output into something like this:</p><pre><code>GET: https://boardgamegeek.com/xmlapi/boardgame/175947,154416,207903,235550,166713,170362,154414,135064,154427,164663,166715,235552,194840,218784,176267,175950,252891,154426,218785,252889,175946,189948,231759,154425,154419,166711,202178,175951,194838,164662,175945,235551,189949,218783,203755,181306,207901,164664,189952,160425,161069,163672,227264,194841,207615,102875,349067,357292,357293,357291?stats=1</code></pre><p>Yes, you need to make a separate request to get the actual details of each game. Fortunately you can send multiple identifiers at once but this can still be very slow which has led me to chunk the results into groups of 50 that are then fired in parallel (so 6 additional requests in this example for a total of 7 overall). Due to the search being sorted alphabetically, it is necessary to get the details of every single game that is returned before you can filter or sort them appropriately.</p><p>This would obviously be a nightmare if you were sending these requests after every keystroke so hopefully this long-winded journey through debouncing now makes a lot more sense &#128514;.</p><p>It was this project that got me to finally knuckle down and learn the newer await/async concurrency model as doing all this via blocks and completion handlers would have been a nightmare. Instead, I can have something like this:</p><pre><code>private func performBoardGameGeekRequest&lt;R: APIRequest, T: Codable&gt;(_ request: R) async throws -&gt; [T] {
    let response = try await client.send(request)
    let code = response.httpStatusCode
    switch code {
    case 202:
        os_log("Need to repeat request", log: OSLog.networking, type: .error)
        let count = (cache[request.identifier] ?? 0) + 1
        cache[request.identifier] = count
        if count &gt;= 3 {
            throw APIError.noData
        }
        try await Task.sleep(nanoseconds: 5_000_000_000)
        return try await performBoardGameGeekRequest(request)
    default:
        cache.removeValue(forKey: request.identifier)
    }

    guard let data = response.data else {
        throw APIError.noData
    }
    return await parseBoardGameGeekXML(data)
}</code></pre><p>This single function can be used in multiple places. Thanks to the use of generics, I can throw it an API request and the model I&#8217;m expecting to be returned and it will handle if the request needs to be retried (and limit that to a certain number of retries) and the conversion of XML to my own Swift models. At a higher level, there are functions like this:</p><pre><code>private func fetchBoardGames&lt;R: APIRequest&gt;(with request: R) async throws -&gt; [BoardGameGeekGame] {
    let identifiers: [Int] = try await performBoardGameGeekRequest(request)
    os_log("Fetched %d identifiers", log: OSLog.networking, type: .info, identifiers.count)
    let chunkedIdentifiers = identifiers.chunked(into: 50)
    
    return await withTaskGroup(of: [BoardGameGeekGame].self, body: { group in
        
        var games = [BoardGameGeekGame]()
        games.reserveCapacity(identifiers.count)
        
        for ids in chunkedIdentifiers {
            group.addTask {
                let request = BoardGameGeekAPI.Game.get(identifiers: ids)
                return (try? await self.performBoardGameGeekRequest(request)) ?? []
            }
        }
        
        for await partialGames in group {
            games.append(contentsOf: partialGames)
        }
        
        return games
    })
}</code></pre><p>This will take a request (be it for a search or looking at a specific user&#8217;s games) and then fetch just the identifiers before doing a batched lookup on each of those games in parallel and returning them as a usable model that can then be filtered locally.</p><p>Once again, the beauty of these async/await functions is that they can hide a complex chain of requests in a single line that can be cancelled simply. Network cancellation is relatively rare (how often do you see UI in an app to cancel loading something?) but it&#8217;s amazing to look at something as simple as a search page to find that you may need to cancel multiple requests with every key the user enters.</p><p>Unfortunately my journey with the BoardGameGeek API is not yet over as my next task for <em>Board Game Lists</em> is to create the concept of a &#8220;collection&#8221;. If the user provides a BoardGameGeek username, then the app will keep their online collection in sync with the app so if you add a game to your collection on their website it will be reflected within the collection list in the app; this will then be filterable via a number of smart lists so you can pull out something like &#8220;games suitable for 2-4 players with an average playtime of under 2 hours and a rating of over 4 stars&#8221;.</p><p>At least it will be something different; after adding it to 3 apps this month, I&#8217;m a little bored of the &#8220;Search &amp; Add&#8221; screens now &#129315;</p><h1>Apple TV to iPhone communication</h1><p>I mentioned earlier the dichotomy of being excited for the new things at WWDC whilst also dreading the amount of work it potentially generates. This is exacerbated at this time of year by looking back and seeing all the new APIs that were released in 2022 that I haven&#8217;t made use of.</p><p>One of those new features was the <a href="https://developer.apple.com/documentation/devicediscoveryui">DeviceDiscoveryUI framework </a>which allows an Apple TV app to connect and communicate with an iPhone, iPad, or Apple Watch. A good example of this would be how the Apple Watch communicates with the Apple Fitness app:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kBMw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad78d9b-3692-40d6-ae4b-3d541266af56_980x551.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kBMw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad78d9b-3692-40d6-ae4b-3d541266af56_980x551.jpeg 424w, https://substackcdn.com/image/fetch/$s_!kBMw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad78d9b-3692-40d6-ae4b-3d541266af56_980x551.jpeg 848w, https://substackcdn.com/image/fetch/$s_!kBMw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad78d9b-3692-40d6-ae4b-3d541266af56_980x551.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!kBMw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad78d9b-3692-40d6-ae4b-3d541266af56_980x551.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kBMw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad78d9b-3692-40d6-ae4b-3d541266af56_980x551.jpeg" width="980" height="551" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7ad78d9b-3692-40d6-ae4b-3d541266af56_980x551.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:551,&quot;width&quot;:980,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:94814,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kBMw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad78d9b-3692-40d6-ae4b-3d541266af56_980x551.jpeg 424w, https://substackcdn.com/image/fetch/$s_!kBMw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad78d9b-3692-40d6-ae4b-3d541266af56_980x551.jpeg 848w, https://substackcdn.com/image/fetch/$s_!kBMw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad78d9b-3692-40d6-ae4b-3d541266af56_980x551.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!kBMw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad78d9b-3692-40d6-ae4b-3d541266af56_980x551.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://www.apple.com/uk/newsroom/2022/09/apple-fitness-plus-available-to-all-iphone-users-in-21-countries-later-this-fall/">Image &#169; 2022 Apple</a></figcaption></figure></div><p>It&#8217;s not necessarily a fair comparison as whilst you might expect them to be the same, the DeviceDiscoveryUI framework has a number of restrictions:</p><ul><li><p>It only works on tvOS (so you can&#8217;t communicate between an Apple Watch and an iPad like Apple Fitness can)</p></li><li><p>It only works on Apple TV 4K (Apple Fitness can work with Apple TV HD)</p></li><li><p>The tvOS app can only connect to one device at a time (i.e. you couldn&#8217;t make a game with this that used two iPhones as controllers)</p></li><li><p>The tvOS app can only connect to other versions of your app that share the same bundle identifier (and are thus sold with <a href="https://developer.apple.com/support/universal-purchase/">Universal Purchase</a>)</p></li><li><p>This will not work on either the tvOS or iOS simulators. You must use physical devices.</p></li></ul><p>The UI for the connection setup is also different to Apple Fitness as we will see shortly.</p><p>My use case for this technology is a bit convoluted as I was really looking for an excuse to use it rather than the best fit. I have a personal app named <em><a href="https://bendodson.com/weblog/2021/03/10/stoutness/">Stoutness</a></em> that I use on my Apple TV every morning to give me a briefing on my day whilst I do my chiropractic stretches. Using shortcuts and various apps on my iPhone, I send a ton of data to my server which the Apple TV app then fetches and uses. The app also communicates directly with some 3rd party APIs such as YouTube and Pocket.</p><p>One of the main reasons for the app is to get me to work through my backlogs of games, books, videos, and articles by having the app randomly pick from my various lists and presenting them to me; I then know &#8220;out of the 4 books I&#8217;m currently reading, I should read <em>x</em> today&#8221;<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a>. The problem is that later in the day I often forget what the app had decided I should use, a particular problem when it suggests 5 articles for me to read from a backlog of about 200 &#128556;. Whilst I cache this information daily in the Apple TV app, it's a bit of a pain to fire it up just to skip through a few screens and remember what I should be reading. Surely this information would be better on my phone?</p><p>The obvious way to do this would be for the server to make the calls to Pocket and YouTube and then store the cache in my database along with the random choices of games and books. An iOS app could then download that in the same way the tvOS app does. This is true, but it&#8217;s not as fun as learning a new framework and having my phone connect to the Apple TV to a) send all the data that my shortcuts used to do directly and b) have the cache be sent back in response ready to be used on iOS.</p><p>After a brief look at the docs, I naively assumed this would be done in an hour as it looked vaguely similar to the way in which an iPhone app can talk to an embedded Apple Watch app or a Safari extension via two way messaging. After 4 hours, I finally got something working but it does not feel as solid as I would like&#8230;</p><p>Apple provide a developer article titled &#8220;<a href="https://developer.apple.com/documentation/devicediscoveryui/connecting_a_tvos_app_to_other_devices_over_the_local_network?changes=late__6__8">Connecting a tvOS app to other devices over the local network</a>&#8221; that sounds like it should be exactly what we need. It details how we present the connection UI (in both SwiftUI and UIKit), how to listen for the connection on iOS / iPadOS / watchOS, and how to initiate the connection. However, there are two issues with this article.</p><p>First of all, most of the code in it doesn&#8217;t actually compile or has been written incorrectly. The SwiftUI code references a &#8220;device name&#8221; variable which isn&#8217;t present<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-8" href="#footnote-8" target="_self">8</a>, fails to include the required "fallback" view block (for displaying on unsupported devices like the Apple TV HD), and presents the device picker behind a connect button failing to notice that the picker itself has it&#8217;s own connect button which sits transparently above the one you just pressed.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iF6y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5bb6bc4-847a-4aa6-9094-f35561d8204d_1200x675.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iF6y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5bb6bc4-847a-4aa6-9094-f35561d8204d_1200x675.gif 424w, https://substackcdn.com/image/fetch/$s_!iF6y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5bb6bc4-847a-4aa6-9094-f35561d8204d_1200x675.gif 848w, https://substackcdn.com/image/fetch/$s_!iF6y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5bb6bc4-847a-4aa6-9094-f35561d8204d_1200x675.gif 1272w, https://substackcdn.com/image/fetch/$s_!iF6y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5bb6bc4-847a-4aa6-9094-f35561d8204d_1200x675.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iF6y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5bb6bc4-847a-4aa6-9094-f35561d8204d_1200x675.gif" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c5bb6bc4-847a-4aa6-9094-f35561d8204d_1200x675.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:675,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:640789,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!iF6y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5bb6bc4-847a-4aa6-9094-f35561d8204d_1200x675.gif 424w, https://substackcdn.com/image/fetch/$s_!iF6y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5bb6bc4-847a-4aa6-9094-f35561d8204d_1200x675.gif 848w, https://substackcdn.com/image/fetch/$s_!iF6y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5bb6bc4-847a-4aa6-9094-f35561d8204d_1200x675.gif 1272w, https://substackcdn.com/image/fetch/$s_!iF6y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5bb6bc4-847a-4aa6-9094-f35561d8204d_1200x675.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>For the UIKit code, it references an <code>NWEndpointPickerViewController</code> which doesn&#8217;t exist. The correct name is <code>DDDevicePickerViewController</code>.</p><p>Once the actual picker is presented, things start to look very promising. You get a fullscreen view that shows your app icon with a privacy string that you define within Info.plist on the left hand side whilst any applicable devices are listed on the right hand side:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RNvZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76de8179-f932-402d-bac6-6000cfbc8efe_3840x2160.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RNvZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76de8179-f932-402d-bac6-6000cfbc8efe_3840x2160.jpeg 424w, https://substackcdn.com/image/fetch/$s_!RNvZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76de8179-f932-402d-bac6-6000cfbc8efe_3840x2160.jpeg 848w, https://substackcdn.com/image/fetch/$s_!RNvZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76de8179-f932-402d-bac6-6000cfbc8efe_3840x2160.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!RNvZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76de8179-f932-402d-bac6-6000cfbc8efe_3840x2160.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RNvZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76de8179-f932-402d-bac6-6000cfbc8efe_3840x2160.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/76de8179-f932-402d-bac6-6000cfbc8efe_3840x2160.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:955397,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!RNvZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76de8179-f932-402d-bac6-6000cfbc8efe_3840x2160.jpeg 424w, https://substackcdn.com/image/fetch/$s_!RNvZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76de8179-f932-402d-bac6-6000cfbc8efe_3840x2160.jpeg 848w, https://substackcdn.com/image/fetch/$s_!RNvZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76de8179-f932-402d-bac6-6000cfbc8efe_3840x2160.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!RNvZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76de8179-f932-402d-bac6-6000cfbc8efe_3840x2160.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>An important thing to note here is that the devices do not necessarily have your app installed, they are merely devices <em>potentially capable</em> of running your app.</p><p>When we initiate a connection to an iPhone, a notification is displayed. The wording can&#8217;t be controlled and will be different depending on whether the corresponding app is installed or not:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7sOC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c3d733-edb7-4389-a6b5-059c15e37911_2960x2936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7sOC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c3d733-edb7-4389-a6b5-059c15e37911_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!7sOC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c3d733-edb7-4389-a6b5-059c15e37911_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!7sOC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c3d733-edb7-4389-a6b5-059c15e37911_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!7sOC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c3d733-edb7-4389-a6b5-059c15e37911_2960x2936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7sOC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c3d733-edb7-4389-a6b5-059c15e37911_2960x2936.png" width="1456" height="1444" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e8c3d733-edb7-4389-a6b5-059c15e37911_2960x2936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1444,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:14355781,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7sOC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c3d733-edb7-4389-a6b5-059c15e37911_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!7sOC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c3d733-edb7-4389-a6b5-059c15e37911_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!7sOC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c3d733-edb7-4389-a6b5-059c15e37911_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!7sOC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c3d733-edb7-4389-a6b5-059c15e37911_2960x2936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Connection notification request for iOS from tvOS both with and without the app installed. If the app is installed, the notification uses the Apple TV name for the title (&#8220;Office&#8221; in this case).</figcaption></figure></div><p>You seem to have around 30 seconds to accept the connection otherwise the tvOS interface goes back a step and you need to send a new request. If you do not have the app installed, tapping the notification will take you to the App Store page.</p><p>We now come to the second problem in Apple&#8217;s documentation:</p><blockquote><p>As soon as the user selects a device, the system passes you an <code>NWEndpoint</code>. Use this endpoint to connect to the selected device. Create an <code>NWConnection</code>, passing it both the endpoint and the parameters that you used to create the device picker view. <strong>You can then use this connection to send or receive messages to the connected device.</strong></p></blockquote><p>The emphasis above is mine. This is the extent of the documentation on how to actually use the connection to send and receive messages. It turns out that the connection uses classes from the <a href="https://developer.apple.com/documentation/networkextension/hotspot_helper/in-provider_networking?changes=late__6__8">In-Provider Networking</a> that was introduced in iOS 9 specifically for network extensions. In fact, this is still the case according to the documentation:</p><blockquote><p>These APIs have the following key characteristics:</p><ul><li><p>They aren&#8217;t general-purpose APIs; they can only be used in the context of a NetworkExtension provider or hotspot helper.</p></li></ul></blockquote><p>There is zero documentation on how to use these APIs in the context of Apple TV to iOS / iPadOS / WatchOS communication &#129318;&#127995;&#8205;&#9794;.</p><p>In terms of sending messages, there is only one method aptly named <code>send(content:contentContext:isComplete:completion:)</code>. This allows us to send any arbitrary <code>Data</code> such as a JSON-encoded string.</p><p>The real problem is how to <em>receive</em> those messages. There is a method named <code>receiveMessage(completion:)</code> which, based on my work with watchOS and iOS extensions, sounds promising. Apple describes it as &#8220;schedules a single receive completion handler for a complete message, as opposed to a range of bytes&#8221;. Perfect!</p><p>Except it isn&#8217;t called, at least not when a message is sent. In a somewhat frustrating act, the messages only appear once the connection is terminated either because the tvOS app stops or because I cancel the connection. I tried for multiple hours but could not get that endpoint to fire unless the entire connection was dropped (at which point any messages that were sent during that time would come through as one single piece of data). I can only assume the messages are being cached locally without being delivered yet when the connection drops it suddenly decides to unload them &#129335;&#127995;&#8205;&#9794;.</p><p>It turns out you need to use the more complex <code>receive(minimumIncompleteLength:maximumLength:completion:)</code> which requires you to say how big you want batches of data to be. You also need to resubscribe to this handler every time data appears on it. The problem here is that whilst there is a &#8220;completion&#8221; flag to tell you if the whole message has arrived this is never true when sending from tvOS, even if you use the corresponding flag on the send method. In the end, I limited the app to 1MB of data at a time as everything I send is well below that. I&#8217;ve never run into a problem with only partial data being sent but it is a potential risk to be aware of.</p><p>If you were using this for critical data, I&#8217;d probably suggest only sending encoded text and providing your own delimiter to look for i.e. for each string that comes in batch them together until one ends in a &#8220;|||&#8221; at which point you will know that was the end of a message from tvOS.</p><p>On the positive side, the connection setup and data sending are near instantaneous and the user facing UI works well. However, as there were already low-level network solutions to send data between devices (including non-Apple devices) it&#8217;s incredibly odd to me that Apple went to the effort of creating a beautiful device pairing API and UI for both SwiftUI and UIKit but didn&#8217;t extend that to the basics of sending data. Local networking is hard. I have no interest in diving into the minutia of handling UDP packets; I just want to send some basic strings between devices!</p><p>Judging by the complete lack of 3rd party apps using this feature or articles detailing how to use this API I&#8217;m going to go out on a limb and say it&#8217;s unlikely we&#8217;ll see any improvements to this system in tvOS 17. Regardless, I&#8217;ve filed a few bug reports in the hopes that the documentation can be tidied up a bit. I&#8217;ve also written <a href="https://bendodson.com/weblog/2023/05/10/connecting-a-tvos-app-to-ios-ipados-and-watchos-with-devicediscoveryui/">an instructional blog post</a> on the subject and published a <a href="https://github.com/bendodson/LocalDeviceManager">demo project on GitHub</a> for those that are interested in implementing it. Just be aware that this is not the robust solution I was hoping it would be!</p><h1>Recommendations</h1><h4>Video Games</h4><ul><li><p><em><strong><a href="https://store.steampowered.com/app/1562430/DREDGE/">Dredge</a></strong></em> - Sail around some islands doing some fishing to upgrade your boat which allows you catch more fish which allows a better boat which means more fish and so on. That&#8217;s only half of it though. The other half is the story. A Lovecraftian story. Excellent on the Steam Deck.</p></li><li><p><em><strong><a href="https://layashorizon.com">Laya&#8217;s Horizon</a></strong></em> - From the creators of <em>Alto&#8217;s Adventure</em> (probably my favourite mobile game) comes a new 3D adventure in a very similar mould. You glide down a mountain discovering new places to stop, picking up collectibles, and engaging in races and other aerial activities. The exploration is of particular interest to me with the map revealing itself as you descend the 360&#186; area. Well worth a try. Available for free on iOS and Android with a Netflix subscription.</p></li><li><p><em><strong><a href="https://store.steampowered.com/app/2263010/Pineapple_on_pizza/">Pineapple on Pizza</a></strong></em> - This is an odd one. It&#8217;s free, it&#8217;ll last about 10-15 minutes, but it&#8217;s ending is very entertaining and has a catchy tune. I played it on Steam Deck. Give it a try without reading anything about it would be my suggestion!</p></li></ul><h4>Books</h4><ul><li><p><em><strong><a href="https://www.waterstones.com/book/grave-expectations/alice-bell/9781838958398">Grave Expectations</a></strong></em> - The debut novel of Alice Bell, host of one of my favourite podcasts on Rock Paper Shotgun (<a href="https://www.rockpapershotgun.com/topics/podcast">The Electronic Wireless Show</a>). It&#8217;s a murder mystery in a big old <em>Knives Out</em> house attempting to be solved by a millenial medium and the teenage ghost that is attached to her. It&#8217;s hilarious but also has a great mystery at the heart of it. Highly recommended.</p></li></ul><h4>Movies</h4><ul><li><p><em><strong><a href="https://www.imdb.com/title/tt6718170/">The Super Mario Bros. Movie</a></strong></em> - I completely forgot to mention this in the last issue which isn&#8217;t a glowing endorsement &#128514;. If you&#8217;re a Mario fan or you have kids then you should watch it (and probably have, twice), otherwise give it a miss. It&#8217;s not a kids film like <em>The Lego Movie</em> with mass appeal for both kids and adults but instead a relatively safe romp through The Mushroom Kingdom filled with nostalgic music and bright colours.</p></li></ul><h4>TV</h4><ul><li><p><em><strong><a href="https://www.amazon.co.uk/placeholder_title/dp/B0B8JNN347">Jury Duty</a></strong></em> - A documentary about a US jury. The twist is that every one &#8211; the jury, the guards, the judge, the witnesses, the lawyers &#8211; are all actors; except one juror who does not realise the whole thing is fake. Absolutely hilarious. I was worried it would be more cringe than amusing but it&#8217;s genuinely very funny. James Marsden gets a special mention for starring as himself. I hope there is a behind-the-scenes of this at some point as there must have been a phenomenal amount of work to pull this off. </p></li></ul><h1>Roadmap</h1><p>The roadmap is my way of committing to what I&#8217;m going to do before the next issue:</p><h4>13th April - 10th May</h4><ul><li><p>Complete v2.1 of <em>Music Library Tracker &#10060;</em></p></li><li><p>Finish the main list feature of <em>Board Game Lists &#9989;</em></p></li></ul><p>It&#8217;s been a very busy month so I&#8217;m not surprised I didn&#8217;t get v2.1 completed. With my renewed enthusiasm after detailing what is going to go into the rebranded v3 update I&#8217;m now ready to have something more manageable ready for next time!</p><h4>11th May - 7th June (Issue #10)</h4><ul><li><p>Complete the Lossless Audio integration in <em>Music Library Tracker </em>v3</p></li><li><p>Get the BoardGameGeek collection sync complete in <em>Board Game Lists</em></p></li><li><p>Build something with the new iOS 17 SDK (or RealityOS &#128064;)</p></li></ul><p>That wraps it up for this issue. I hope you found something of interest and that you&#8217;ll be able to recommend the newsletter to your friends, family, and colleagues. You can always comment on this issue or email me directly via <a href="mailto:ben@bendodson.com">ben@bendodson.com</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/subscribe?"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/9-the-waiting-place/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/9-the-waiting-place/comments"><span>Leave a comment</span></a></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Thanks Amazon Prime (the same-day delivery service, not the video streaming &#128514;)</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>When I say &#8220;now decided&#8221; I do literally mean &#8220;now&#8221;. I was just finishing off writing this section when I realised this really should be a bigger release.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>OK, it&#8217;s not <em>every</em> Audio Variant. I&#8217;m pointedly ignoring &#8220;Lossy Stereo&#8221;.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>So the tabs would be &#8220;Activity&#8221;, &#8220;Spatial Audio&#8221;, &#8220;Lossless Audio&#8221; or maybe shortened to just &#8220;Activity&#8221;, &#8220;Spatial&#8221;, &#8220;Lossless&#8221;&#8230;</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>The API was built for iTunes music before bolting on iBooks. For that reason, the fields for title and author are amusingly named trackName and artistName respectively &#128514;</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>BoardGameGeek has details of all the various expansion packs so a large property like Lord of the Rings will undoubtedly have hundreds of results especially when you get into living card games. A modern API would let you ignore these results but there&#8217;s no such luck here!</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p>I go into <em>why</em> I do this in an <a href="https://bendodson.com/weblog/2021/03/10/stoutness/">article I wrote about the app</a> back in 2021.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-8" href="#footnote-anchor-8" class="footnote-number" contenteditable="false" target="_self">8</a><div class="footnote-content"><p>I have been unable to divine a way to get the name of the device you are connected to.</p></div></div>]]></content:encoded></item><item><title><![CDATA[#8: Pick & Mix]]></title><description><![CDATA[A health scare leads to a re-appraisal of my wellbeing apps]]></description><link>https://www.thedododeveloper.com/p/8-healthy-living</link><guid isPermaLink="false">https://www.thedododeveloper.com/p/8-healthy-living</guid><pubDate>Wed, 12 Apr 2023 13:45:48 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/62245184-24bf-4613-85c9-ae0775a24a01_1000x750.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dear Reader,</p><p>After some routine blood tests a few months ago, it transpires that I&#8217;m at the upper range of prediabetes and very likely beyond that into a formal diagnosis of Type 2 diabetes. This wasn&#8217;t exactly a surprise given the amount of sugar, alcohol, and carbohydrates I was consuming (or the <a href="https://bendodson.s3-eu-west-1.amazonaws.com/weblog/2023/picknmixkg.jpeg">kilogram bag of Pick &amp; Mix</a> I was given on a recent trip to Sweden &#128556;). However, I was starting to experience some of the symptoms that typically don&#8217;t appear until it&#8217;s been ongoing for some time so decided I needed to make some fairly drastic changes.</p><p>For the past few weeks I&#8217;ve been drinking more water, eating less food (along with a better diet), and exercising properly which has led to losing nearly a stone and making some notable improvements to my heart. To do this, I&#8217;ve been using a number of apps including some private ones I created just for me which I&#8217;ll be showcasing in this issue.</p><p>Until Apple manage to crack non-invasive blood glucose monitoring, the best thing you can do if you suspect you may have prediabetes is to get a blood test either from your GP or from one of the many at-home test kits that are available (I personally use <a href="https://www.numan.com/fear-nothing/fear-nothing-blood-test">Numan</a>). It is reversible so don&#8217;t put it off!</p><p>In other news, I haven&#8217;t been focussed on a single app this month but have instead been doing small bits and pieces on a number of different projects; ironically a bit of a Pick &amp; Mix. I&#8217;ll be going through those along with a brief review of some new HomeKit equipment I set up over the Easter break.</p><p>The temporary monthly cadence for this newsletter has been working well so I&#8217;m going to stick to that as I&#8217;m being pulled in multiple directions by client work, my own projects, and my new fitness regime. There are over 700 subscribers now so a big thank you to all of you; as always, you can comment on this issue or <a href="mailto:ben@bendodson.com">email me directly</a> if you have any questions or feedback.</p><p><strong>&#8212; Ben</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/8-healthy-living/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/8-healthy-living/comments"><span>Leave a comment</span></a></p><h1>Contents</h1><ul><li><p>My Health Apps</p></li><li><p>Lists in Game Track</p></li><li><p>Music Library Tracker v2.0.2</p></li><li><p>Receipt Printing from macOS</p></li><li><p>Waking Up Shortcut</p></li><li><p>HomeKit Secure Video</p></li><li><p>Substack Notes</p></li><li><p>Recommendations</p></li><li><p>Roadmap</p></li></ul><h1>My Health Apps</h1><p>As those of you that have been reading for a while will know, I&#8217;m a big fan of logging and tracking data over time. This is especially important with health and is something that became a lot easier when Apple introduced the Health app and HealthKit framework back in iOS 8. As you might expect, I use a number of apps to load data into Health but I also have a few bespoke apps that I&#8217;ve created for my own use:</p><h2>Scales</h2><p>Weight is generally regarded as one of the most important markers of health due to the way that most negative health conditions are greatly exacerbated by or have their chance increased by being overweight. I have over a decades worth of weight logs since I purchased a set of <a href="https://www.amazon.co.uk/Fitbit-Aria-Air-Smart-Scale/dp/B07VV6GKDS/?tag=bendodsoncom-21&amp;th=1">FitBit Aria scales</a> back in 2012 and I have synced these into the Health app as well as to my own database. The scales eventually broke in 2019 so I replaced them with a non-smart set and manually added my weight into the Health app. </p><p>However, one of the things that irritates me most about Health is the way it measures weight being either in kilograms, pounds, or stones. You&#8217;d think the &#8220;stones&#8221; option would be the typical stones and pounds (i.e. 14st 10lbs) but instead it&#8217;s a decimalised option (i.e. 14.71st). I don&#8217;t know why anybody would choose to measure weight that way &#128514;</p><p>To remedy this, I decided to build myself a small app that would show me a graph of my weight and allow me to input new weights in the <em>*correct*</em> format. The whole thing would be synced via HealthKit but would also work with my own database so I could potentially use that data in other places.</p><p>The result was <em>Scales</em>, one of my first forays into SwiftUI back in 2020 and a masterclass in why you shouldn&#8217;t build your own graphing system<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Mt0x!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff986e9-bd73-40cc-8542-4859f109565a_2960x2936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Mt0x!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff986e9-bd73-40cc-8542-4859f109565a_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!Mt0x!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff986e9-bd73-40cc-8542-4859f109565a_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!Mt0x!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff986e9-bd73-40cc-8542-4859f109565a_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!Mt0x!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff986e9-bd73-40cc-8542-4859f109565a_2960x2936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Mt0x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff986e9-bd73-40cc-8542-4859f109565a_2960x2936.png" width="1456" height="1444" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dff986e9-bd73-40cc-8542-4859f109565a_2960x2936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1444,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:650682,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Mt0x!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff986e9-bd73-40cc-8542-4859f109565a_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!Mt0x!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff986e9-bd73-40cc-8542-4859f109565a_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!Mt0x!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff986e9-bd73-40cc-8542-4859f109565a_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!Mt0x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff986e9-bd73-40cc-8542-4859f109565a_2960x2936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The graph view opens on the latest piece of data but can scroll horizontally back to 2012:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!d8H7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa15c274-f241-40d9-b63f-ba102585c2a1_7860x1200.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!d8H7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa15c274-f241-40d9-b63f-ba102585c2a1_7860x1200.png 424w, https://substackcdn.com/image/fetch/$s_!d8H7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa15c274-f241-40d9-b63f-ba102585c2a1_7860x1200.png 848w, https://substackcdn.com/image/fetch/$s_!d8H7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa15c274-f241-40d9-b63f-ba102585c2a1_7860x1200.png 1272w, https://substackcdn.com/image/fetch/$s_!d8H7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa15c274-f241-40d9-b63f-ba102585c2a1_7860x1200.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!d8H7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa15c274-f241-40d9-b63f-ba102585c2a1_7860x1200.png" width="1456" height="222" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fa15c274-f241-40d9-b63f-ba102585c2a1_7860x1200.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:222,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:187978,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!d8H7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa15c274-f241-40d9-b63f-ba102585c2a1_7860x1200.png 424w, https://substackcdn.com/image/fetch/$s_!d8H7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa15c274-f241-40d9-b63f-ba102585c2a1_7860x1200.png 848w, https://substackcdn.com/image/fetch/$s_!d8H7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa15c274-f241-40d9-b63f-ba102585c2a1_7860x1200.png 1272w, https://substackcdn.com/image/fetch/$s_!d8H7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa15c274-f241-40d9-b63f-ba102585c2a1_7860x1200.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Whilst I have data throughout that period, it didn&#8217;t necessarily all fall on the same day and sometimes I might not weigh myself for a couple of months. To account for that, I wrote a smoothing algorithm to basically format the data as if I&#8217;d weighed myself once a week; this works by looking at the difference in weight between two dates and then effectively faking the data at set intervals based on the average increase or decrease (i.e. if I&#8217;d weighed myself 28 days after my last weigh and had gained 4lbs, then the data would show I gained 1lb per week).</p><p>The horizontal lines show half-stone intervals. Those highlighted in green are instead goals; short-term (14st 7lbs), mid-term (13st 7lbs), and long-term (12st 10lbs). Each of these goals can be changed on the server side and will then adjust as necessary within the app. Similarly, if I suddenly put on an extra stone, the graph would expand upwards to account for the new min/max of the data.</p><p>Originally I had a touch interaction so you could drag the red circle back through the data to see the exact weight at a given time. The performance on this way very poor due to my complete naaivity around how SwiftUI rendered its layout so I removed this feature. If I was building this today, I&#8217;d use <a href="https://developer.apple.com/documentation/charts">Swift Charts</a> with an overlay.</p><p>The plus button in the top right hand corner invokes the &#8220;New Weight&#8221; sheet which is in desperate need of a redesign. When I created this app back in 2020, I didn&#8217;t want to use keyboard input so instead made the input selection based on context menus. Now I think I would use a stepper control as the amount is very unlikely to differ by more than four steps from the current value and I already have to do four taps with the menu interface. I&#8217;d also change this to use a half-height sheet rather than the full-height one shown here.</p><p>For the app icon I used <em><a href="https://jimdo-storage.freetls.fastly.net/image/37514951/ecbac2bf-41bb-448b-adbf-92d0cced4f5f.jpg?format=pjpg&amp;quality=80,90&amp;auto=webp&amp;disable=upscale&amp;width=1440&amp;height=1440">Plus Sized Arial</a></em><a href="https://jimdo-storage.freetls.fastly.net/image/37514951/ecbac2bf-41bb-448b-adbf-92d0cced4f5f.jpg?format=pjpg&amp;quality=80,90&amp;auto=webp&amp;disable=upscale&amp;width=1440&amp;height=1440"> by @Neoqlassical</a> and then took the colours from Arial&#8217;s hair, outfit, and scales<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> (red, purple, and green) to use within the app.</p><h2>Chaise Longue to 5k</h2><p>As somebody who is desperately unfit, I appreciate that I can&#8217;t just suddenly go from doing nothing to running a marathon<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>. That's where the Couch to 5k program comes in. Originally invented by <a href="https://bigmedium.com/ideas/bbc-how-josh-clark-invented-couch-to-5k.html">Josh Clark</a>, the idea is that you do a mixture of walking and running over 9 weeks with the aim being to comfortably run continuously for 30 minutes by the end. The program is now <a href="https://www.nhs.uk/live-well/exercise/running-and-aerobic-exercises/get-running-with-couch-to-5k/">endorsed by the NHS</a> who provide <a href="https://apps.apple.com/gb/app/one-you-couch-to-5k/id1082307672">their own app</a> which I used when I first tried started running in 2019.</p><p>Whilst the NHS app and the many others available on the App Store all work well, they usually run on your iPhone or Apple Watch and communicate whether you should walk or run via haptics or audio prompts. This is great if you&#8217;re doing this outside but I run on a treadmill in my garage with an Apple TV in front of me so I wondered if I could make something a bit different.</p><p>The result was <em>Chaise Longue to 5K</em>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!klnC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7dd0d76-006a-43fb-b9c9-7db9480a56e9_1920x1080.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!klnC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7dd0d76-006a-43fb-b9c9-7db9480a56e9_1920x1080.jpeg 424w, https://substackcdn.com/image/fetch/$s_!klnC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7dd0d76-006a-43fb-b9c9-7db9480a56e9_1920x1080.jpeg 848w, https://substackcdn.com/image/fetch/$s_!klnC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7dd0d76-006a-43fb-b9c9-7db9480a56e9_1920x1080.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!klnC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7dd0d76-006a-43fb-b9c9-7db9480a56e9_1920x1080.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!klnC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7dd0d76-006a-43fb-b9c9-7db9480a56e9_1920x1080.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d7dd0d76-006a-43fb-b9c9-7db9480a56e9_1920x1080.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:250861,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!klnC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7dd0d76-006a-43fb-b9c9-7db9480a56e9_1920x1080.jpeg 424w, https://substackcdn.com/image/fetch/$s_!klnC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7dd0d76-006a-43fb-b9c9-7db9480a56e9_1920x1080.jpeg 848w, https://substackcdn.com/image/fetch/$s_!klnC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7dd0d76-006a-43fb-b9c9-7db9480a56e9_1920x1080.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!klnC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7dd0d76-006a-43fb-b9c9-7db9480a56e9_1920x1080.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">This is not me. I&#8217;d seriously injure myself on one of these slim treadmills.</figcaption></figure></div><p>The killer feature is that the run can be shrunk down to a Picture in Picture window so I can watch YouTube or Plex whilst on my run but still clearly see how long I have left in each section:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!49jr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660b9e91-e1ee-4aed-b325-7148580493b2_1920x1080.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!49jr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660b9e91-e1ee-4aed-b325-7148580493b2_1920x1080.jpeg 424w, https://substackcdn.com/image/fetch/$s_!49jr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660b9e91-e1ee-4aed-b325-7148580493b2_1920x1080.jpeg 848w, https://substackcdn.com/image/fetch/$s_!49jr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660b9e91-e1ee-4aed-b325-7148580493b2_1920x1080.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!49jr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660b9e91-e1ee-4aed-b325-7148580493b2_1920x1080.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!49jr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660b9e91-e1ee-4aed-b325-7148580493b2_1920x1080.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/660b9e91-e1ee-4aed-b325-7148580493b2_1920x1080.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:125787,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!49jr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660b9e91-e1ee-4aed-b325-7148580493b2_1920x1080.jpeg 424w, https://substackcdn.com/image/fetch/$s_!49jr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660b9e91-e1ee-4aed-b325-7148580493b2_1920x1080.jpeg 848w, https://substackcdn.com/image/fetch/$s_!49jr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660b9e91-e1ee-4aed-b325-7148580493b2_1920x1080.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!49jr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660b9e91-e1ee-4aed-b325-7148580493b2_1920x1080.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Whilst it was originally designed for Apple TV, I also ported it to iPhone, iPad, and macOS with the same PiP functionality. Buy it once, and it becomes available on all those platforms with syncing of your data through iCloud. No analytics, no privacy invasion, just a simple app to let you do a Couch to 5K run with a Picture in Picture window!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://apps.apple.com/us/app/chaise-longue-to-5k/id1633434180&quot;,&quot;text&quot;:&quot;Download Chaise Longue to 5K&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://apps.apple.com/us/app/chaise-longue-to-5k/id1633434180"><span>Download Chaise Longue to 5K</span></a></p><p><em>* I&#8217;ve put the app on a 25% discount for the next week. The Dodo Developer Diabetes Discount ends on the 20th April!</em></p><p></p><h2>Om-nom-nomivore</h2><p>Exercise is all very well and good but it&#8217;s food that is typically my downfall and that needs to be monitored a lot more closely now. Much as I love tracking, I&#8217;m not a fan of apps like <a href="https://www.myfitnesspal.com">MyFitnessPal</a> that you use to log everything you&#8217;ve consumed. The databases they use aren&#8217;t always accurate and they can be a pain if you are cooking your own meals from scratch or eating in restaurants, etc. I also find they make you think about food a lot more.</p><p>Instead, I decided to create myself a basic app for meal planning. I&#8217;ll typically have a weekly shop delivered by Ocado and then use the app to divide everything into my meals based on the expiration dates:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4ED2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d22d115-5a03-4433-8fa9-72dc60a868d8_4137x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4ED2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d22d115-5a03-4433-8fa9-72dc60a868d8_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!4ED2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d22d115-5a03-4433-8fa9-72dc60a868d8_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!4ED2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d22d115-5a03-4433-8fa9-72dc60a868d8_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!4ED2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d22d115-5a03-4433-8fa9-72dc60a868d8_4137x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4ED2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d22d115-5a03-4433-8fa9-72dc60a868d8_4137x2716.png" width="1456" height="956" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2d22d115-5a03-4433-8fa9-72dc60a868d8_4137x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:956,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1314139,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4ED2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d22d115-5a03-4433-8fa9-72dc60a868d8_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!4ED2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d22d115-5a03-4433-8fa9-72dc60a868d8_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!4ED2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d22d115-5a03-4433-8fa9-72dc60a868d8_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!4ED2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d22d115-5a03-4433-8fa9-72dc60a868d8_4137x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The upcoming week is shown in a list with a section for each day broken down into breakfast, lunch, and dinner. Tapping on a meal time will bring up a list of relevant meals which I can add via the &#8226;&#8226;&#8226;&nbsp;button.</p><p>As regular readers will know, I&#8217;m a big fan of using side projects to learn new technologies so one feature I added with this release was the ability to drag and drop meals between days allowing for quick rearranging; this allowed me to get to grips with the way the newer drag and drop APIs work in UIKit.</p><p>One of the slightly odd features of this app is that the data is not stored in a local cache or database, but instead within the iOS calendar! The reason for this will become apparent when we delve into the <em>Stoutness</em> app shortly but it was also a fun exercise in using <a href="https://developer.apple.com/documentation/eventkit">EventKit</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WztI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F463a41b5-bba2-413d-8dbd-3e49f7552182_1339x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WztI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F463a41b5-bba2-413d-8dbd-3e49f7552182_1339x2716.png 424w, https://substackcdn.com/image/fetch/$s_!WztI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F463a41b5-bba2-413d-8dbd-3e49f7552182_1339x2716.png 848w, https://substackcdn.com/image/fetch/$s_!WztI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F463a41b5-bba2-413d-8dbd-3e49f7552182_1339x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!WztI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F463a41b5-bba2-413d-8dbd-3e49f7552182_1339x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WztI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F463a41b5-bba2-413d-8dbd-3e49f7552182_1339x2716.png" width="236" height="478.6975354742345" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/463a41b5-bba2-413d-8dbd-3e49f7552182_1339x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2716,&quot;width&quot;:1339,&quot;resizeWidth&quot;:236,&quot;bytes&quot;:714424,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WztI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F463a41b5-bba2-413d-8dbd-3e49f7552182_1339x2716.png 424w, https://substackcdn.com/image/fetch/$s_!WztI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F463a41b5-bba2-413d-8dbd-3e49f7552182_1339x2716.png 848w, https://substackcdn.com/image/fetch/$s_!WztI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F463a41b5-bba2-413d-8dbd-3e49f7552182_1339x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!WztI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F463a41b5-bba2-413d-8dbd-3e49f7552182_1339x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">That tag syntax will be important shortly&#8230;</figcaption></figure></div><p>Whilst the main list is stored within the Calendar app, the meals themselves are all stored in NSUbiquitousKeyValueStore based on their meal time so I get seamless syncing between devices thanks to iCloud. These three lists are all stored using an <a href="https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU">LRU algorithm</a> so the meal I used last is always at the top (I&#8217;m a fussy eater that really only eats about 10 different meals so the last meal I ate is likely the next meal I&#8217;m going to eat).</p><p>This app is a good example of another philosophical idea I like to bang on about; <em>building things for oneself</em>. I&#8217;d never release an app like this as it&#8217;s fulfilling a very particular need for me but that&#8217;s OK. It&#8217;s nice to be able to build little tools that work just for you and they can be a good way to develop your skills and learn new technologies.</p><p>The final thing to mention on this one is that the app name is based on a <a href="https://www.threadless.com/shop/@threadless/design/omnomnomnivore/mens/t-shirt/regular?color=navy">T-shirt I got from Threadless</a> years ago. I extracted the artwork to use for the app icon and then used the navy background and the pink of the dinosaur for the theme colours.</p><h2>Stoutness</h2><p>Continuing with &#8220;weird apps built just for me&#8221;, <em><a href="https://bendodson.com/weblog/2021/03/10/stoutness/">Stoutness</a></em> is a tvOS app I created in 2021 to help me remember to do my chiropractic stretches. It&#8217;s essentially a collection of 12 data views with a video explaining a particular stretch bolted to the side.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6J4w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbd66df3-5f79-4c4f-b316-57a01a027b7e_1600x900.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6J4w!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbd66df3-5f79-4c4f-b316-57a01a027b7e_1600x900.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6J4w!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbd66df3-5f79-4c4f-b316-57a01a027b7e_1600x900.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6J4w!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbd66df3-5f79-4c4f-b316-57a01a027b7e_1600x900.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6J4w!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbd66df3-5f79-4c4f-b316-57a01a027b7e_1600x900.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6J4w!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbd66df3-5f79-4c4f-b316-57a01a027b7e_1600x900.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fbd66df3-5f79-4c4f-b316-57a01a027b7e_1600x900.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:159589,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6J4w!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbd66df3-5f79-4c4f-b316-57a01a027b7e_1600x900.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6J4w!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbd66df3-5f79-4c4f-b316-57a01a027b7e_1600x900.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6J4w!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbd66df3-5f79-4c4f-b316-57a01a027b7e_1600x900.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6J4w!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbd66df3-5f79-4c4f-b316-57a01a027b7e_1600x900.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0Y78!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20fdabfa-aaff-451d-91b2-a80ca24ebe77_1600x900.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0Y78!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20fdabfa-aaff-451d-91b2-a80ca24ebe77_1600x900.jpeg 424w, https://substackcdn.com/image/fetch/$s_!0Y78!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20fdabfa-aaff-451d-91b2-a80ca24ebe77_1600x900.jpeg 848w, https://substackcdn.com/image/fetch/$s_!0Y78!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20fdabfa-aaff-451d-91b2-a80ca24ebe77_1600x900.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!0Y78!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20fdabfa-aaff-451d-91b2-a80ca24ebe77_1600x900.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0Y78!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20fdabfa-aaff-451d-91b2-a80ca24ebe77_1600x900.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/20fdabfa-aaff-451d-91b2-a80ca24ebe77_1600x900.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:163916,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0Y78!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20fdabfa-aaff-451d-91b2-a80ca24ebe77_1600x900.jpeg 424w, https://substackcdn.com/image/fetch/$s_!0Y78!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20fdabfa-aaff-451d-91b2-a80ca24ebe77_1600x900.jpeg 848w, https://substackcdn.com/image/fetch/$s_!0Y78!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20fdabfa-aaff-451d-91b2-a80ca24ebe77_1600x900.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!0Y78!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20fdabfa-aaff-451d-91b2-a80ca24ebe77_1600x900.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Jb3Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F045a321c-41e3-4d61-ac1d-12a6c73ad292_1600x900.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Jb3Y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F045a321c-41e3-4d61-ac1d-12a6c73ad292_1600x900.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Jb3Y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F045a321c-41e3-4d61-ac1d-12a6c73ad292_1600x900.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Jb3Y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F045a321c-41e3-4d61-ac1d-12a6c73ad292_1600x900.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Jb3Y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F045a321c-41e3-4d61-ac1d-12a6c73ad292_1600x900.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Jb3Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F045a321c-41e3-4d61-ac1d-12a6c73ad292_1600x900.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/045a321c-41e3-4d61-ac1d-12a6c73ad292_1600x900.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:160087,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Jb3Y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F045a321c-41e3-4d61-ac1d-12a6c73ad292_1600x900.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Jb3Y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F045a321c-41e3-4d61-ac1d-12a6c73ad292_1600x900.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Jb3Y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F045a321c-41e3-4d61-ac1d-12a6c73ad292_1600x900.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Jb3Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F045a321c-41e3-4d61-ac1d-12a6c73ad292_1600x900.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Each page showcases a different piece of data which is pulled in via the coding equivalent of duct tape.</p><ul><li><p><strong><a href="https://bendodson.s3.amazonaws.com/weblog/2021/stoutness-newspapers.jpg">Newspapers</a>:</strong> A custom PHP scraper that fetches the front pages from <a href="https://tomorrowspapers.co.uk">tomorrowspapers.co.uk</a></p></li><li><p><strong><a href="https://bendodson.s3.amazonaws.com/weblog/2021/stoutness-tasks.jpg">Tasks</a>: </strong>An AppleScript runs on my Mac Studio every morning to export the tasks from within Things and put them in a plist file that the app downloads</p></li><li><p><strong><a href="https://bendodson.s3.amazonaws.com/weblog/2021/stoutness-schedule.jpg">Schedule</a>: </strong>My morning Shortcut exports the day&#8217;s events from the iOS calendar and then breaks them down into sections of the day (those <code>[food:breakfast]</code> tags I mentioned within <em>Om-nom-nomivore</em> should make more sense now!)</p></li><li><p><strong><a href="https://bendodson.s3.amazonaws.com/weblog/2021/stoutness-sleep.jpg">Sleep</a>:</strong> I track my sleep with AutoSleep which is opened automatically when I wake up (more on that later) and synced to my database via a Shortcut</p></li><li><p><strong><a href="https://bendodson.s3.amazonaws.com/weblog/2021/stoutness-alcohol.jpg">Alcohol</a>:</strong> I created a custom Shortcut that allows me to choose alcohol type (wine or beer<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>), ABV, and drink size (ml). It then calculates the UK units and stores that in my database. I typically invoke it from a widget to the left of my homescreen</p></li><li><p><strong><a href="https://bendodson.s3.amazonaws.com/weblog/2021/stoutness-steps.jpg">Steps</a>:</strong> Extracted from HealthKit using a custom app I wrote<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a> and then synced up to my database with my morning Shortcut</p></li><li><p><strong><a href="https://bendodson.s3.amazonaws.com/weblog/2021/stoutness-exercise.jpg">Exercise</a>:</strong> As above, but with exercise minutes instead</p></li><li><p><strong><a href="https://bendodson.s3.amazonaws.com/weblog/2021/stoutness-youtube.jpg">YouTube</a>:</strong> The app downloads a list of videos from my public &#8220;To Watch&#8221; playlist and then picks a random selection for me</p></li><li><p><strong><a href="https://bendodson.s3.amazonaws.com/weblog/2021/stoutness-articles.jpg">Pocket</a>:</strong> Similar to YouTube, this connects to my Pocket account and picks a random selection of articles</p></li><li><p><strong><a href="https://bendodson.s3.amazonaws.com/weblog/2021/stoutness-projects.jpg">Coding</a>:</strong> The time spent on my personal projects is populated from the Toggl API (filtered to a specific &#8220;client&#8221;) but my database will randomly pick one of my active projects for me to work on</p></li><li><p><strong><a href="https://bendodson.s3.amazonaws.com/weblog/2021/stoutness-reading.jpg">Reading</a>: </strong>This one is populated via the <em>Bookmark</em> app I showcased back in <a href="https://www.thedododeveloper.com/i/94296031/bookmark">Issue #3</a>. Again, my server picks a random book that I should read today</p></li><li><p><strong><a href="https://bendodson.s3.amazonaws.com/weblog/2021/stoutness-games.jpg">Games</a>: </strong>Another custom app, this is populated via <em>Game Track</em> which I mentioned in <a href="https://www.thedododeveloper.com/i/95046946/game-track">Issue #4</a> and later in this issue!</p></li></ul><p>I love this app as it helps me do my basic stretches but also gets me set up for the day by showing me the day&#8217;s news, an overview of my key health metrics, and randomly choosing what I should watch, read, play, and work on. In case you&#8217;re wondering about that last bit, I&#8217;ve long subscribed to the philosophy of &#8220;idea sex&#8221;, the process of consuming or working on multiple things at once in a bid to have creativity spark more frequently due to holding multiple concepts in your mind at once. As I have several books on the go at once, it&#8217;s helpful to be told &#8220;this is the one to read today&#8221; as it stops me from just sticking with whatever I&#8217;m enjoying the most.</p><p>When I finished working on <em>Stoutness</em> back in 2021, <a href="https://bendodson.com/weblog/2021/03/10/stoutness/">I said</a>:</p><blockquote><p>As of the time of writing, I&#8217;ve spent 24.4 hours working on the Stoutness app from its origins as a printed daily report to the Apple TV app it is now complete with its suite of data providers from AppleScript apps to Shortcut workflows. Since using it daily from the start of the year I&#8217;ve had huge improvements in nearly all of the metrics I track; I&#8217;m sleeping longer, drinking less, exercising more, and reading more. I&#8217;ve also noticed big improvements in my health with my daily stretching leading to more flexibility and less aches and my average resting heart rate falling from ~70bpm to ~50bpm. Finally, I&#8217;ve lost over 22lbs since using the app which is just over 10% of my starting weight; that isn&#8217;t all attributable to this app but it has definitely helped by encouraging me to keep working on the various rings that I see every morning.</p></blockquote><p>You can see the huge drop (and the resulting climb back up after my 2nd child was born &#128514;) this caused in the weight graph from Scales that I showcased earlier. I&#8217;m hoping this app will work for me again!</p><h2>Browser Note</h2><p>Whilst not necessarily an app related to health, I initially created <em><a href="https://apps.apple.com/us/app/browser-note/id1613527249">Browser Note</a></em> to help me order less when I ordered a takeaway:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WI4A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F875372c3-c6f8-4049-b38a-521dcf0079ac_3840x2160.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WI4A!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F875372c3-c6f8-4049-b38a-521dcf0079ac_3840x2160.jpeg 424w, https://substackcdn.com/image/fetch/$s_!WI4A!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F875372c3-c6f8-4049-b38a-521dcf0079ac_3840x2160.jpeg 848w, https://substackcdn.com/image/fetch/$s_!WI4A!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F875372c3-c6f8-4049-b38a-521dcf0079ac_3840x2160.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!WI4A!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F875372c3-c6f8-4049-b38a-521dcf0079ac_3840x2160.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WI4A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F875372c3-c6f8-4049-b38a-521dcf0079ac_3840x2160.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/875372c3-c6f8-4049-b38a-521dcf0079ac_3840x2160.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:172933,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WI4A!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F875372c3-c6f8-4049-b38a-521dcf0079ac_3840x2160.jpeg 424w, https://substackcdn.com/image/fetch/$s_!WI4A!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F875372c3-c6f8-4049-b38a-521dcf0079ac_3840x2160.jpeg 848w, https://substackcdn.com/image/fetch/$s_!WI4A!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F875372c3-c6f8-4049-b38a-521dcf0079ac_3840x2160.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!WI4A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F875372c3-c6f8-4049-b38a-521dcf0079ac_3840x2160.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It&#8217;s an app that does one thing incredibly well; it adds fullscreen notes to webpages. It does this by making use of an iOS 15 Safari Extension that you can activate within Safari to add your note and later update or delete it. When a note is displayed on a page, an optional &#8220;View Website&#8221; button is available to let you continue into the site; when tapped, the note is snoozed for a period of time at which point it will appear again on any subsequent reloads (the snooze period is 15 minutes but you can change this within the app).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://apps.apple.com/us/app/browser-note/id1613527249" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Fpnd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9da99561-e628-4fc2-9671-2dfa9fc10189_3840x2160.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Fpnd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9da99561-e628-4fc2-9671-2dfa9fc10189_3840x2160.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Fpnd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9da99561-e628-4fc2-9671-2dfa9fc10189_3840x2160.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Fpnd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9da99561-e628-4fc2-9671-2dfa9fc10189_3840x2160.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Fpnd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9da99561-e628-4fc2-9671-2dfa9fc10189_3840x2160.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9da99561-e628-4fc2-9671-2dfa9fc10189_3840x2160.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:260878,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;https://apps.apple.com/us/app/browser-note/id1613527249&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Fpnd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9da99561-e628-4fc2-9671-2dfa9fc10189_3840x2160.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Fpnd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9da99561-e628-4fc2-9671-2dfa9fc10189_3840x2160.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Fpnd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9da99561-e628-4fc2-9671-2dfa9fc10189_3840x2160.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Fpnd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9da99561-e628-4fc2-9671-2dfa9fc10189_3840x2160.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>After using this solution for a week or two, I quickly found myself wanting to use it in on other sites such as BBC News and Twitter to prevent me from habitually visiting them. I developed the extension further so I could add notes directly within Safari and instantly saw my phone usage drop by several hours a week. It turns out that adding a short &#8220;are you sure&#8221; dialogue before visiting a website is a great way to break the habit!</p><p>The app is available on iOS, iPadOS, and macOS; one purchase unlocks it on all those platforms and your data is synced securely and privately through iCloud. No analytics, no data sent to my server or anyone else, just a secure way to give you positive behavioural nudges within Safari.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://apps.apple.com/us/app/browser-note/id1613527249&quot;,&quot;text&quot;:&quot;Download Browser Note&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://apps.apple.com/us/app/browser-note/id1613527249"><span>Download Browser Note</span></a></p><p><em>* I&#8217;ve put the app on a 50% discount for the next week. The Dodo Developer Diabetes Discount ends on the 20th April!</em></p><div><hr></div><p>So those are my health apps! As I mentioned earlier, I&#8217;m already seeing noticeable benefits having lost nearly a stone already but I&#8217;m also seeing interesting metric developments within the Health app. For example, I got a notification last week telling me my resting heart rate had a new trend in that it had dropped by 20% (and is still dropping):</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!k4ko!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa13196-2da9-4d70-bc38-7de471b1de36_1290x1123.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!k4ko!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa13196-2da9-4d70-bc38-7de471b1de36_1290x1123.jpeg 424w, https://substackcdn.com/image/fetch/$s_!k4ko!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa13196-2da9-4d70-bc38-7de471b1de36_1290x1123.jpeg 848w, https://substackcdn.com/image/fetch/$s_!k4ko!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa13196-2da9-4d70-bc38-7de471b1de36_1290x1123.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!k4ko!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa13196-2da9-4d70-bc38-7de471b1de36_1290x1123.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!k4ko!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa13196-2da9-4d70-bc38-7de471b1de36_1290x1123.jpeg" width="320" height="278.5736434108527" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cfa13196-2da9-4d70-bc38-7de471b1de36_1290x1123.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1123,&quot;width&quot;:1290,&quot;resizeWidth&quot;:320,&quot;bytes&quot;:207033,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!k4ko!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa13196-2da9-4d70-bc38-7de471b1de36_1290x1123.jpeg 424w, https://substackcdn.com/image/fetch/$s_!k4ko!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa13196-2da9-4d70-bc38-7de471b1de36_1290x1123.jpeg 848w, https://substackcdn.com/image/fetch/$s_!k4ko!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa13196-2da9-4d70-bc38-7de471b1de36_1290x1123.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!k4ko!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfa13196-2da9-4d70-bc38-7de471b1de36_1290x1123.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This is something that I would never notice day-to-day and is only made possible by all the additional tracking that is done mostly silently by the Apple Watch. It also showcases that whilst it&#8217;s important to log as many key metrics as you can, it&#8217;s absolutely crucial that you review that data in order to see what effects any changes you are making are having.</p><h1>Lists in Game Track</h1><p>I&#8217;m a big fan of the downtime organiser app <em><a href="https://www.sofahq.com">Sofa</a></em>. I primarily use it as a way of storing my backlogs of games, books, and tv shows with the idea being that they&#8217;ll be deleted from <em>Sofa</em> and moved to one of my own tracking apps when I actually start engaging with the media. For example, I might have a list of books I want to read within <em>Sofa</em> but once I start reading something it goes into my own <em>Bookmark</em> app. This in turn is then output into my <em>Stoutness</em> app when picking what book I should read today.</p><p>You may remember my <em>Game Track</em> app from <a href="https://www.thedododeveloper.com/i/95046946/game-track">Issue #4</a>. As a brief reminder, it&#8217;s an app that my friend John and I use to log the time we spend playing video games. Up until now, I&#8217;ve been storing games I want to play or want to buy in separate lists in <em>Sofa</em> (based on platform) until such a time as I start playing them. Whilst <em>Game Track</em> has a way to log gaming sessions, they aren&#8217;t the same as books in <em>Bookmark</em> as a game doesn&#8217;t have a defined finish; it isn&#8217;t possible to look at the current playtime and see that you are 40% through<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>. This means that I do not have a way to clearly see what games I have on my plate right now, only games I've recently played. </p><p>For the past few years I've manually added games to a database table so they'll show up within <em>Stoutness</em> but I often forget to update it and I don't have a clear view of what's in there as it's all based on numeric identifiers. Whilst I could use the new Shortcuts feature within <em>Sofa</em> to pull out games from a &#8220;currently playing&#8221; list, it would be much nicer to be able to do this within my own app hence a new lists feature I&#8217;ve been working on.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VKX5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085b82b7-76c1-493a-b66c-32e3d1180e88_2738x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VKX5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085b82b7-76c1-493a-b66c-32e3d1180e88_2738x2716.png 424w, https://substackcdn.com/image/fetch/$s_!VKX5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085b82b7-76c1-493a-b66c-32e3d1180e88_2738x2716.png 848w, https://substackcdn.com/image/fetch/$s_!VKX5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085b82b7-76c1-493a-b66c-32e3d1180e88_2738x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!VKX5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085b82b7-76c1-493a-b66c-32e3d1180e88_2738x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VKX5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085b82b7-76c1-493a-b66c-32e3d1180e88_2738x2716.png" width="552" height="547.4505494505495" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/085b82b7-76c1-493a-b66c-32e3d1180e88_2738x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1444,&quot;width&quot;:1456,&quot;resizeWidth&quot;:552,&quot;bytes&quot;:3217680,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!VKX5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085b82b7-76c1-493a-b66c-32e3d1180e88_2738x2716.png 424w, https://substackcdn.com/image/fetch/$s_!VKX5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085b82b7-76c1-493a-b66c-32e3d1180e88_2738x2716.png 848w, https://substackcdn.com/image/fetch/$s_!VKX5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085b82b7-76c1-493a-b66c-32e3d1180e88_2738x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!VKX5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085b82b7-76c1-493a-b66c-32e3d1180e88_2738x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The initial feature set is fairly basic. You can create a list, and you can add games to it. Simple! </p><p>I particularly liked using a new feature in iOS 16 to make it so the title of the list can be edited inline<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a> along with filtering the list below by platform:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GgS2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b25b5f2-9ae4-479a-9439-caac83678c04_2738x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GgS2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b25b5f2-9ae4-479a-9439-caac83678c04_2738x2716.png 424w, https://substackcdn.com/image/fetch/$s_!GgS2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b25b5f2-9ae4-479a-9439-caac83678c04_2738x2716.png 848w, https://substackcdn.com/image/fetch/$s_!GgS2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b25b5f2-9ae4-479a-9439-caac83678c04_2738x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!GgS2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b25b5f2-9ae4-479a-9439-caac83678c04_2738x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GgS2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b25b5f2-9ae4-479a-9439-caac83678c04_2738x2716.png" width="536" height="531.5824175824176" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9b25b5f2-9ae4-479a-9439-caac83678c04_2738x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1444,&quot;width&quot;:1456,&quot;resizeWidth&quot;:536,&quot;bytes&quot;:1897993,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GgS2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b25b5f2-9ae4-479a-9439-caac83678c04_2738x2716.png 424w, https://substackcdn.com/image/fetch/$s_!GgS2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b25b5f2-9ae4-479a-9439-caac83678c04_2738x2716.png 848w, https://substackcdn.com/image/fetch/$s_!GgS2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b25b5f2-9ae4-479a-9439-caac83678c04_2738x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!GgS2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b25b5f2-9ae4-479a-9439-caac83678c04_2738x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In <em>Sofa</em> I used to organise my lists via platform so I&#8217;d have "Games To Buy &gt; Switch&#8221;, &#8220;Games To Buy &gt; PC&#8221;, etc. That led to lots of lists but also an issue whereby a game might appear on multiple platforms and thus end up in multiple lists; more than once I ended up getting a game I already owned elsewhere &#128556;</p><p>Instead of having multiple lists per platform, I decided to choose which platforms I was interested in for a game and then use filters within the list to restrict the view. That way I can easily see which games I want to buy on Switch but then if I end up removing it from the list I won&#8217;t see the same game as being listed to buy on Xbox. To do this, I refactored the platform selection view (which is used when choosing a game to log time against) and enabled multiple selection of platforms. As the platforms are fetched from <a href="https://api-docs.igdb.com/#getting-started">IGDB</a> and aren&#8217;t necessarily up to date, I also added an &#8220;Additional Platforms&#8221; button to let me choose from my own list of platforms including things like the Steam Deck which IGDB would lump in with &#8220;PC&#8221;:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!y_7q!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e074e95-75e9-49d5-a086-5375a38d17c2_2738x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!y_7q!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e074e95-75e9-49d5-a086-5375a38d17c2_2738x2716.png 424w, https://substackcdn.com/image/fetch/$s_!y_7q!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e074e95-75e9-49d5-a086-5375a38d17c2_2738x2716.png 848w, https://substackcdn.com/image/fetch/$s_!y_7q!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e074e95-75e9-49d5-a086-5375a38d17c2_2738x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!y_7q!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e074e95-75e9-49d5-a086-5375a38d17c2_2738x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!y_7q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e074e95-75e9-49d5-a086-5375a38d17c2_2738x2716.png" width="562" height="557.3681318681319" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3e074e95-75e9-49d5-a086-5375a38d17c2_2738x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1444,&quot;width&quot;:1456,&quot;resizeWidth&quot;:562,&quot;bytes&quot;:594239,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!y_7q!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e074e95-75e9-49d5-a086-5375a38d17c2_2738x2716.png 424w, https://substackcdn.com/image/fetch/$s_!y_7q!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e074e95-75e9-49d5-a086-5375a38d17c2_2738x2716.png 848w, https://substackcdn.com/image/fetch/$s_!y_7q!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e074e95-75e9-49d5-a086-5375a38d17c2_2738x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!y_7q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e074e95-75e9-49d5-a086-5375a38d17c2_2738x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now that I had a list of games I was currently playing, the next step was to update my <em>Stoutness</em> app to use it. <a href="https://bendodson.s3.amazonaws.com/weblog/2021/stoutness-games.jpg">Previously</a> the app was picking a random game from a manually edited database of games I was playing and one from games I had in my backlog. The reason for this is that I&#8217;m dreadful for starting multiple games and then just playing the new shiny thing rather than finishing anything; with this system, I would always play at least 30 minutes of the game that was selected ensuring I was always gradually finishing them. One issue with this was that occasionally games would be picked that I wouldn&#8217;t be able to play for one reason or another; for example, it might pick a game on my Xbox but my wife may have been using the TV during our downtime preventing me from playing that game. To remedy that, I&#8217;ve broken down each platform into three categories &#8211; TV (Xbox), Desktop (PC, Mac), and Handheld (Steam Deck, Switch, iOS) &#8211; and the app will randomly pick a game from each one of those platforms:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BTbr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4db83a9-7f41-4c01-b38b-1feff8d99fe4_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BTbr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4db83a9-7f41-4c01-b38b-1feff8d99fe4_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!BTbr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4db83a9-7f41-4c01-b38b-1feff8d99fe4_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!BTbr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4db83a9-7f41-4c01-b38b-1feff8d99fe4_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!BTbr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4db83a9-7f41-4c01-b38b-1feff8d99fe4_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BTbr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4db83a9-7f41-4c01-b38b-1feff8d99fe4_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f4db83a9-7f41-4c01-b38b-1feff8d99fe4_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2811738,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BTbr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4db83a9-7f41-4c01-b38b-1feff8d99fe4_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!BTbr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4db83a9-7f41-4c01-b38b-1feff8d99fe4_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!BTbr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4db83a9-7f41-4c01-b38b-1feff8d99fe4_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!BTbr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4db83a9-7f41-4c01-b38b-1feff8d99fe4_1920x1080.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>With this system in place, I now get three random games from my currently playing list spread across the various platforms I can play on.</p><p>The final issue I had with lists in <em>Sofa</em> was related to games I wanted to buy. I rarely buy games on the Xbox or Switch; the former has Game Pass and the latter is only good for first party Nintendo titles which tend to bypass &#8220;to buy&#8221; straight to &#8220;currently playing&#8221; on release. That means the vast majority of my &#8220;to buy&#8221;&nbsp;list are PC games of which 99.9% will be bought through Steam. This leads to a problem of maintaining two lists as I&#8217;d typically add a game to both <em>Sofa</em> and my Steam wishlist making it very easy to go out of sync.</p><p>To solve that, I&#8217;ve written a PHP script which will check my Steam wishlist every day and then amend my &#8220;to buy&#8221; list accordingly be that adding a game or removing it. I&#8217;ve made it smart enough so if I&#8217;ve added a game on multiple platforms it will only remove the Steam-relevant ones (PC, Mac, Steam Deck) but I&#8217;ve also added a push notification to let me know if a Steam game appears on Game Pass. Unfortunately there is not a public write API for the Steam Wishlist so I can&#8217;t automatically remove games if they are deleted from Game Track. To deal with that, I throw an alert when I try to delete a game reminding me that I&#8217;ll need to do it via Steam&#8217;s app.</p><p>Whilst I still have a few features I&#8217;d like to add, I&#8217;m very pleased with how this has turned out. I&#8217;ve completely removed all of my games from <em>Sofa</em> (which I still use for all my other media backlogs) and now have an automatically updating wishlist, a clear overview of what I&#8217;m playing, a filterable list of games I want to buy, and a random selection of three games I should play each day &#127918;</p><h1>Music Library Tracker v2.0.2</h1><p>A very brief note to say that I&#8217;ve released a bug fix update to <em>Music Library Tracker</em> that resolves an issue which was causing Spatial Audio tracks to flip-flop between &#8220;downgraded&#8221; and &#8220;upgraded&#8221;. The scanning logic was all working correctly but as I was using a Set rather than an Array it was possible for tracks to be scanned in different orders; if this happened over multiple background processes (i.e. if you had a very large music library) then the app would think a track had been removed from Spatial Audio when in fact it hadn&#8217;t.</p><p>Apologies to all those affected and my sincere thanks to Steve Martin for helping test multiple builds!</p><p>On a related note, I saw a nice mention of the app this week over at <a href="https://www.tomsguide.com/opinion/5-reasons-to-switch-from-spotify-to-apple-music">Tom&#8217;s Guide</a>:</p><blockquote><p><a href="https://itunes.apple.com/us/app/music-tracker-automatic-tracking/id1062583712?ls=1&amp;mt=8">Music Library Tracker</a>&nbsp;is a third-party app for iPhone and iPad that has been developed to integrate with the Apple Music library to track spatial audio content changes. As an Apple Music subscriber, it will automatically find out which of the tracks and albums in your music library are also available in spatial audio without having to carry out lengthy searches of your favorite artist's back catalog. It costs $3.99 / &#163;3.99 via the iOS app store &#8212; but if you don't want to pay, you can&nbsp;follow the <a href="https://twitter.com/newspatialaudio">developer's Twitter account</a>&nbsp;to keep track of new spatial audio releases.</p></blockquote><p>&#129392;</p><h1>Receipt Printing from macOS</h1><p>I mentioned in the <a href="https://www.thedododeveloper.com/i/108383997/focus-modes-and-shortcuts">last issue</a> that I&#8217;d recently bought a receipt printer and was looking to set up a Mac app to send it ZPL commands for custom printing. The printer in question is the <a href="https://systemsprintmedia.co.uk/zebra-label-printers/zebra-zq220-plus.html">Zebra ZQ220 Plus</a> and it turns out it does not use ZPL but the older <a href="https://support.zebra.com/cpws/docs/comtec/PROMAN-CPCL_RevY.pdf">CPCL programming language</a> instead &#129318;&#127995;&#8205;&#9794;</p><p>In any case, I was able to knock up a quick native SwiftUI macOS app to send commands to the printer. I haven&#8217;t done much with it yet but the basics are:</p><ol><li><p>Use a <code>CBCentralManager</code> instance to scan for Bluetooth peripherals and display them in a list (I automatically remove anything not matching the name format for a Zebra printer so I don&#8217;t have lists of all the devices in my house)</p></li><li><p>Connect to the peripheral and request it&#8217;s services based on a specific UUID for these types of printers ("<code>38EB4A80-C570-11E3-9507-0002A5D5C51B</code>")</p></li><li><p>Check it&#8217;s services to see if there is a corresponding write characteristic based on a specific UUID ("<code>38EB4A82-C570-11E3-9507-0002A5D5C51B</code>")</p></li><li><p>Send a string of CPCL data to the peripheral from step 1 via the write characteristic from step 3</p></li><li><p>The printer prints!</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!msmP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a210a8-5cd4-4c0a-ac49-38e445378912_588x331.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!msmP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a210a8-5cd4-4c0a-ac49-38e445378912_588x331.gif 424w, https://substackcdn.com/image/fetch/$s_!msmP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a210a8-5cd4-4c0a-ac49-38e445378912_588x331.gif 848w, https://substackcdn.com/image/fetch/$s_!msmP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a210a8-5cd4-4c0a-ac49-38e445378912_588x331.gif 1272w, https://substackcdn.com/image/fetch/$s_!msmP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a210a8-5cd4-4c0a-ac49-38e445378912_588x331.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!msmP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a210a8-5cd4-4c0a-ac49-38e445378912_588x331.gif" width="588" height="331" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/51a210a8-5cd4-4c0a-ac49-38e445378912_588x331.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:331,&quot;width&quot;:588,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:638083,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!msmP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a210a8-5cd4-4c0a-ac49-38e445378912_588x331.gif 424w, https://substackcdn.com/image/fetch/$s_!msmP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a210a8-5cd4-4c0a-ac49-38e445378912_588x331.gif 848w, https://substackcdn.com/image/fetch/$s_!msmP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a210a8-5cd4-4c0a-ac49-38e445378912_588x331.gif 1272w, https://substackcdn.com/image/fetch/$s_!msmP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a210a8-5cd4-4c0a-ac49-38e445378912_588x331.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!87NX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F082b5c4a-b62c-4c9f-bd88-f9cee2c9813b" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!87NX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F082b5c4a-b62c-4c9f-bd88-f9cee2c9813b 424w, https://substackcdn.com/image/fetch/$s_!87NX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F082b5c4a-b62c-4c9f-bd88-f9cee2c9813b 848w, https://substackcdn.com/image/fetch/$s_!87NX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F082b5c4a-b62c-4c9f-bd88-f9cee2c9813b 1272w, https://substackcdn.com/image/fetch/$s_!87NX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F082b5c4a-b62c-4c9f-bd88-f9cee2c9813b 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!87NX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F082b5c4a-b62c-4c9f-bd88-f9cee2c9813b" width="612" height="459" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/082b5c4a-b62c-4c9f-bd88-f9cee2c9813b&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1092,&quot;width&quot;:1456,&quot;resizeWidth&quot;:612,&quot;bytes&quot;:1243565,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!87NX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F082b5c4a-b62c-4c9f-bd88-f9cee2c9813b 424w, https://substackcdn.com/image/fetch/$s_!87NX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F082b5c4a-b62c-4c9f-bd88-f9cee2c9813b 848w, https://substackcdn.com/image/fetch/$s_!87NX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F082b5c4a-b62c-4c9f-bd88-f9cee2c9813b 1272w, https://substackcdn.com/image/fetch/$s_!87NX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F082b5c4a-b62c-4c9f-bd88-f9cee2c9813b 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And that&#8217;s about it for now. I&#8217;ve still not decided fully what I&#8217;m actually going to <em>do</em> with this printer yet, I&#8217;ve just always wanted one for some reason &#128514; It might be that I&#8217;ll set the app up with some kind of notification trigger so I can issue print commands, perhaps in the morning to create a list of tasks. I also need to get to grips with the <a href="https://support.zebra.com/cpws/docs/comtec/PROMAN-CPCL_RevY.pdf">CPCL Programming Manual</a> which weighs in at 497 pages!</p><p>For now, I&#8217;ve put the code<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-8" href="#footnote-8" target="_self">8</a> for the macOS app <a href="https://github.com/bendodson/macos-zq220-cpcl">up on GitHub</a> and will endeavour to come back to this once I&#8217;ve got a clearer idea of what to do with it.</p><h1>Waking Up Shortcut</h1><p>In the <a href="https://www.thedododeveloper.com/i/108383997/focus-modes-and-shortcuts">last issue</a> I demonstrated how I had created a custom &#8220;Bedtime&#8221; focus mode in order to get around some restrictions in using the built-in Sleep focus mode with Shortcuts (specifically that there was no way of detecting when the mode was disabled as the &#8220;When Waking Up&#8221; command only triggers if you&#8217;re using the full bedtime functionality).</p><blockquote><p>To solve the first problem, I removed the Sleep focus mode entirely and created a new one named &#8220;Bedtime&#8221;. I set it up so that when this is activated my iPhone wallpaper is turned to black and Cinema Mode is enabled on my Apple Watch. I then reverse this when Bedtime is deactivated and rather than having my shortcut run at that point I instead set the automation to open the AutoSleep app; I then have an automation for when AutoSleep is closed which runs my shortcut.</p><p>This works great as now when I wake up I turn off the Bedtime focus mode which immediately opens AutoSleep, I refresh the app so the data is all up to date, and then I swipe back to the home screen at which point my shortcut runs in the Dynamic Island and sends everything to my server. Perfect!</p></blockquote><p>This has all been working fine but I noticed a few days ago whilst digging through the Health app that my Apple Watch Ultra was no longer monitoring my Wrist Temperature. This is because your temperature is only recorded whilst you&#8217;re asleep and have the Sleep focus mode enabled &#129318;&#127995;&#8205;&#9794;</p><p>I obviously want to record this information so I needed to go back to using the Sleep focus mode and come up with a new way to run shortcuts when I wake up<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-9" href="#footnote-9" target="_self">9</a>. Whilst browsing through the available triggers, it occurred to me that my phone is always on a MagSafe charger at night so I can run a shortcut when the phone is no longer charging:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cfNu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7712ebbd-f03a-4135-b5be-8204df76c2d1_2960x2936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cfNu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7712ebbd-f03a-4135-b5be-8204df76c2d1_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!cfNu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7712ebbd-f03a-4135-b5be-8204df76c2d1_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!cfNu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7712ebbd-f03a-4135-b5be-8204df76c2d1_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!cfNu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7712ebbd-f03a-4135-b5be-8204df76c2d1_2960x2936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cfNu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7712ebbd-f03a-4135-b5be-8204df76c2d1_2960x2936.png" width="540" height="535.5494505494505" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7712ebbd-f03a-4135-b5be-8204df76c2d1_2960x2936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1444,&quot;width&quot;:1456,&quot;resizeWidth&quot;:540,&quot;bytes&quot;:2640404,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cfNu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7712ebbd-f03a-4135-b5be-8204df76c2d1_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!cfNu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7712ebbd-f03a-4135-b5be-8204df76c2d1_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!cfNu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7712ebbd-f03a-4135-b5be-8204df76c2d1_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!cfNu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7712ebbd-f03a-4135-b5be-8204df76c2d1_2960x2936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">When my phone is removed from the charger, the &#8220;Waking Up&#8221; shortcut will run</figcaption></figure></div><p>As my phone can be used on chargers throughout the day (most often in my car), I check the current focus after the phone is disconnected from power and then only open the AutoSleep app if the Sleep focus mode is currently set. I then turn off the Sleep focus mode. As per the <a href="https://www.thedododeveloper.com/i/108383997/focus-modes-and-shortcuts">last issue</a>, a number of shortcuts then run once I have left the AutoSleep app.</p><p>This is now a much more &#8220;magical&#8221; experience as previously I was turning off a focus mode in order to get my day started which meant faffing around in Control Center. With this shortcut, I just take my phone off the MagSafe charger and everything runs automagically. Perfect!</p><h1>HomeKit Secure Video</h1><p>I&#8217;ve been a Ring user since 2017 with the Ring Pro doorbell, Floodlight Cam, and Spotlight Cam. Whilst Ring have promised to add HomeKit support multiple times over the years, it has never materialised. For much of this time, I&#8217;ve been using Homebridge on my Mac Studio with the <a href="https://www.npmjs.com/package/homebridge-ring">homebridge-ring plugin</a> to get a basic integration which will trigger my HomePods and provide a video feed on the Apple TV when the doorbell is pressed (along with video feeds for the external cameras). The problem is that this is a poor solution that frequently breaks and is prone to very long delays. My floodlight cam also developed an annoying issue of sounding its siren for no particular reason&#8230;</p><p>Due to this, I&#8217;ve never really been <em>pleased</em> with my Ring setup (especially as I had to pay a subscription fee for each camera) but there wasn&#8217;t anything better out there until HomeKit Secure Video came along in 2019. This allowed video to be streamed directly to your iCloud account ensuring better security but also some fancy new features such as face and parcel detection. Whilst there have been a few supported doorbells over the years, none of them (such as the <a href="https://www.belkin.com/smart-video-doorbell/P-WDC010.html">Wemo Smart Video Doorbell</a>) are available in the UK making them an expensive and risky choice if you&#8217;re shipping them over from the US.</p><p>That was until the surprise announcement of the <a href="https://www.amazon.co.uk/Aqara-Doorbell-Included-Recognition-Automations/dp/B0BPHTL7MG/?tag=bendodsoncom-21">Aqara Video Doorbell G4</a> last month. It&#8217;s nearly half the price of the other HomeKit Secure Video doorbells but also came with additional features such as being able to work purely on 6 x AA batteries. As it was available direct from Amazon, I bought it immediately knowing I could return it easily if there were any problems. I chose to wire it in as it was a straight swap with the Ring Pro; even the drilled holes aligned! So far, I am incredibly impressed. The camera feed is sharp and connects almost instantly whilst the Apple features such as face detection work exactly as advertised. I&#8217;ve seen some people online having connection issues but this seems to be if they&#8217;re running on batteries. Another common complaint is that this is a 1080p landscape camera so parcel detection likely will not work as it probably won&#8217;t see the floor; I wasn&#8217;t too concerned about this as the delivery companies in my area always leave parcels around the back (which I&#8217;ve got covered in a second). One thing to note is that it comes with an indoor chime which I initially didn&#8217;t plug in as I didn&#8217;t have a use for it; this is actually mandatory as it&#8217;s a repeater unit that can house an SD card for local storage. Fortunately you can disable the chime within either the Home or Aqara apps.</p><p>With my doorbell switched over, I started researching if I could get any exterior cameras with the same HomeKit Secure Video support. Previously I&#8217;d only been looking for doorbells so it had completely passed me by that there were a ton of external cameras available. I ended up going for the <a href="https://www.amazon.co.uk/dp/B07W4MY7HL?tag=bendodsoncom-21">eufyCam 2C</a> (&#163;190 on Amazon UK but there is a &#163;30 off voucher currently reducing it to &#163;160) which came as a pack of two battery powered cameras along with a central hub (Homebase 2) that they connect to. The setup is straightforward with the hub being networked via ethernet and then added to the Home app; you then press a button on the hub and one on the camera to link them together<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-10" href="#footnote-10" target="_self">10</a>. The cameras themselves are threaded onto a basic external fixture which means they're easy to take down to charge them (every 6 months allegedly) but also does mean they'd be easy to steal... I mounted mine high up so somebody would at least need a ladder &#128514; I was a bit worried about how the quality would be bearing in mind they're battery powered and do not come with an external light but they have completely surpassed my expectations. The video quality is massively superior to the external Ring cameras, especially the nightvision, and notifications and time to connect are fast. Again, HomeKit Secure Video shines with easy detection of people, vehicles, pets, and parcels along with an easy interface for scrolling back through recorded events.</p><p>Overall, I am very pleased with the switch away from Ring and would definitely recommend trying out the <a href="https://www.amazon.co.uk/Aqara-Doorbell-Included-Recognition-Automations/dp/B0BPHTL7MG/?tag=bendodsoncom-21">Aqara G4</a> and the <a href="https://www.amazon.co.uk/dp/B07W4MY7HL?tag=bendodsoncom-21">eufyCam 2C</a> if you&#8217;re in the market for this kind of equipment.</p><p></p><h1>Substack Notes</h1><p>Yesterday, Substack launched their new &#8220;Notes&#8221; feature which is something of a mixture between traditional social media and a forum:</p><div class="comment" data-attrs="{&quot;url&quot;:&quot;https://open.substack.com/notes&quot;,&quot;commentId&quot;:14527278,&quot;comment&quot;:{&quot;id&quot;:14527278,&quot;date&quot;:&quot;2023-04-12T08:25:49.880Z&quot;,&quot;edited_at&quot;:null,&quot;body&quot;:&quot;Hi Everyone &#128075;&#127995;\n\nI&#8217;m Ben Dodson, an independent app and game developer for all Apple platforms (iOS, iPadOS, tvOS, macOS, and watchOS). I alternate between freelance work and developing my own apps including such things as the Music Library Tracker (the only way to find all Spatial Audio in your library), Browser Note (mindful reminders in your browser), and Chaise Longue to 5K (because couches are so common).\n\nI send a 20-30 minute long newsletter each month going through various development and UX issues I&#8217;ve encountered as well as showcasing what life is like for a freelance developer. The next issue goes out in around 6 hours time and will likely be my longest one yet &#128517;&quot;,&quot;body_json&quot;:{&quot;type&quot;:&quot;doc&quot;,&quot;attrs&quot;:{&quot;schemaVersion&quot;:&quot;v1&quot;},&quot;content&quot;:[{&quot;type&quot;:&quot;paragraph&quot;,&quot;content&quot;:[{&quot;type&quot;:&quot;text&quot;,&quot;text&quot;:&quot;Hi Everyone &#128075;&#127995;&quot;}]},{&quot;type&quot;:&quot;paragraph&quot;,&quot;content&quot;:[{&quot;type&quot;:&quot;text&quot;,&quot;text&quot;:&quot;I&#8217;m Ben Dodson, an independent app and game developer for all Apple platforms (iOS, iPadOS, tvOS, macOS, and watchOS). I alternate between freelance work and developing my own apps including such things as the Music Library Tracker (the only way to find all Spatial Audio in your library), Browser Note (mindful reminders in your browser), and Chaise Longue to 5K (because couches are &quot;},{&quot;type&quot;:&quot;text&quot;,&quot;marks&quot;:[{&quot;type&quot;:&quot;italic&quot;}],&quot;text&quot;:&quot;so&quot;},{&quot;type&quot;:&quot;text&quot;,&quot;text&quot;:&quot; common).&quot;}]},{&quot;type&quot;:&quot;paragraph&quot;,&quot;content&quot;:[{&quot;type&quot;:&quot;text&quot;,&quot;text&quot;:&quot;I send a 20-30 minute long newsletter each month going through various development and UX issues I&#8217;ve encountered as well as showcasing what life is like for a freelance developer. The next issue goes out in around 6 hours time and will likely be my longest one yet &#128517;&quot;}]}]},&quot;restacks&quot;:0,&quot;reaction_count&quot;:0,&quot;attachments&quot;:[],&quot;name&quot;:&quot;Ben Dodson&quot;,&quot;user_id&quot;:1807342,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/67ec1727-af37-4d08-b6f4-8c405b5476cd_512x512.png&quot;,&quot;user_bestseller_tier&quot;:null}}" data-component-name="CommentPlaceholder"></div><p>It&#8217;s a place where you can share links, short posts, quotes, photos, etc. I&#8217;m probably going to use it to post a few sneak peeks of things I&#8217;m working on and to answer any questions you may have.</p><p>To join the conversation you can go to <a href="https://substack.com/notes">substack.com/notes</a> or find the new &#8220;Notes&#8217; tab within the <a href="https://apps.apple.com/us/app/substack-reader/id1581650857">Substack app</a> (make sure you&#8217;ve installed the latest update). As a newsletter subscriber, you&#8217;ll automatically see my notes. Feel free<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-11" href="#footnote-11" target="_self">11</a> to like, reply, or share them!</p><h1>Recommendations</h1><h4>Video Games</h4><ul><li><p><em><strong><a href="https://www.ubisoft.com/en-gb/game/anno/1800">Anno 1800</a></strong></em> - I started playing this on PC in 2021 and absolutely loved it. Last year, I bought all the DLC during the Black Friday period and now I&#8217;m finally back to playing it a bit too much (the game literally told me &#8220;you&#8217;ve been playing for a while - why not go and get a coffee?&#8221; &#128514;). It&#8217;s a city building affair with the twist that you need to balance a number of production lines to fulfil your citizens basic needs. Kind of a cross between <em>Factorio</em> and <em>SimCity</em> with a bit of naval combat thrown in if you want it. I love this game.</p></li><li><p><em><strong><a href="https://teamninja-studio.com/wolong/uk/">Wo Long: Fallen Dynasty</a></strong></em> - A Souls-like game which takes elements of <em>Dark Souls</em> and <em>Sekiro</em> to create an interesting set of fighting mechanics. The spirit meter takes some getting used to but works really well as a way to balance blocking, parrying, stamina, and magic usage. I also really like the system of banners to ensure you keep exploring. A difficult but rewarding game. Available on Game Pass.</p></li><li><p><em><strong><a href="https://store.steampowered.com/app/2324650/The_Murder_of_Sonic_the_Hedgehog/">The Murder of Sonic the Hedgehog</a></strong></em> - This was an April Fool&#8217;s joke that ended up being real. An <em>Ace Attorney</em> style murder mystery in which Sonic is dead and you&#8217;ve got to find out whodunit. I got through it in around 90 minutes and really enjoyed it. It&#8217;s free on Steam, works great on Steam Deck, and is powered by Ink!</p></li></ul><h4>Board Games</h4><p>I spent a few days visiting my good friend John in Stockholm in which we played 8 different board games. I&#8217;ll briefly go through each of them:</p><ol><li><p><em><strong><a href="https://boardgamegeek.com/boardgame/246900/eclipse-second-dawn-galaxy">Eclipse: Second Dawn for the Galaxy</a></strong></em> - If you took the video game <em>Stellaris</em> and made a lighter version of it as a board game you&#8217;d end up with something like this. I love this game so much, especially how it comes with neat little trays to store all the components for a very quick setup process. Easily one of my top 3 board games of all time.</p></li><li><p><em><strong><a href="https://boardgamegeek.com/boardgame/318009/dinosaur-island-rawr-n-write">Dinosaur Island: Rawr &#8216;n Write</a></strong></em> - As the name suggests, this is a roll and write spin-off of <em>Dinosaur Island</em> that I really enjoyed. John&#8217;s take was that it was a bit too complicated for a roll and write but not complex enough to be a full board game; I liked the added complexity myself and am definitely going to be playing a few solo games of this soon.</p></li><li><p><em><strong><a href="https://boardgamegeek.com/boardgame/348406/marvel-dice-throne">Marvel Dice Throne</a></strong></em> - A 1v1 card game with a predesigned deck based around a Marvel character. I chose Dr Strange and had a great time as it felt very much like playing a blue deck in <em>Magic: The Gathering</em> (my particular wheelhouse). The 8 characters in the game weren&#8217;t the ones I&#8217;d pick from the Marvel universe but it was a fun game for an hour.</p></li><li><p><em><strong><a href="https://boardgamegeek.com/boardgame/264220/tainted-grail-fall-avalon">Tainted Grail: The Fall of Avalon</a></strong></em> - This was the largest game we played and one we&#8217;ll likely continue playing online. This is the first of the big narrative games that came with a Choose-your-own-Adventure style book to guide your interactions. It has a really interesting battle mechanic in which you build up chains of cards to determine the outcome with a similar system for diplomacy (I love the idea of diplomacy being a literal battle of words). There was a particularly memorable moment in the game in which I tried unsuccessfully to sway a mob, then failed again, then failed to fight them, then failed at some exploration, before running back in the nick of time with a literal holy grail that finally swayed their opinion of me.</p></li><li><p><em><strong><a href="https://boardgamegeek.com/boardgame/263918/cartographers">Cartographers</a></strong></em> - Another roll and write, this time about drawing maps. I&#8217;ve wanted to play this for quite a while and was not disappointed. There is an iPad version of the game but it&#8217;s a little rough in the design department for my taste. I&#8217;ll definitely pick up the physical copy and play it again.</p></li><li><p><em><strong><a href="https://boardgamegeek.com/boardgame/331582/hexplore-it-domain-mirza-noctis">HEXplore It: The Domain of Mirza Noctis</a></strong></em> - We purchased this game during the weekend and got it all set up but the rules were a little more complex than we&#8217;d bargained for. We played for an hour or so and enjoyed it but we&#8217;d got some of the rules wrong which meant every combat encounter was basically unwinnable. There&#8217;s definitely some good potential here though and I really want to give it another play if we can find a way to transfer it online.</p></li><li><p><em><strong><a href="https://boardgamegeek.com/boardgame/317321/darkest-dungeon-board-game">Darkest Dungeon</a></strong></em> - You may remember me going through an app I&#8217;d built for this in the <a href="https://www.thedododeveloper.com/i/108383997/darkest-dungeon">last issue</a>. I was still working on it right up until the last minute (literally the morning we were due to play) but I&#8217;m happy to say that it worked really well. I&#8217;d added some music and narration from the video game which added the right ambiance (&#8220;Back to the pit!&#8221;) and made the gameplay much faster. Our second quest left a sour taste as the randomness of the exploration meant we ended up taking a lot more damage than we probably should have. I&#8217;m going to play some more of this solo now that I&#8217;ve written the app, but I&#8217;ll probably take a break for a couple of months as I&#8217;m sick of the sight of that rulebook &#128514;</p></li><li><p><em><strong><a href="https://boardgamegeek.com/boardgame/364792/zombicide-gear">Zombicide: Gear Up</a></strong></em> - Our final roll and write of the weekend, this one is based around fighting an oncoming wave of zombies so you&#8217;re drawing damage rather than drawing maps. I&#8217;m not a zombie guy so the theming did nothing for me but I like the gameplay concept.</p></li></ol><h4>Music</h4><ul><li><p><em><strong><a href="https://music.apple.com/gb/album/so-much-for-stardust/1665320666">So Much (For) Stardust</a></strong></em> by Fall Out Boy <em>- </em>I&#8217;ve been a big Fall Out Boy fan for nearly 20 years at this point but I wasn&#8217;t so keen on their last album, 2018&#8217;s <em>Mania</em>. Fortunately their new album is on a par with <em>Save Rock and Roll</em> or (my personal favourite) <em>Folie &#224; deux. </em>There are lots of great tracks on here; <em>Hold Me Like a Grudge</em>, <em>Love From The Other Side</em>, and the pandemic referencing <em>What a Time To Be Alive</em> being particularly notable. I could have done without the Ethan Hawke bit from <em>Reality Bites </em>but I can&#8217;t fault the rest of it. Available in Spatial Audio!</p></li></ul><h4>Books</h4><ul><li><p><em><strong><a href="https://www.amazon.co.uk/Tress-Emerald-Sea-Brandon-Sanderson/dp/1399613375?tag=bendodsoncom-21">Tress of the Emerald Sea</a></strong></em> by Brandon Sanderson - I picked this up as part of the <em><a href="https://www.kickstarter.com/projects/dragonsteel/surprise-four-secret-novels-by-brandon-sanderson">Surprise! Four Secret Novels</a></em><a href="https://www.kickstarter.com/projects/dragonsteel/surprise-four-secret-novels-by-brandon-sanderson"> Kickstarter</a> of which this was the first book released on the 1st January. I can&#8217;t remember the last time I noted so many quotes in my Readwise account for a single book<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-12" href="#footnote-12" target="_self">12</a>! It&#8217;s funny, touching, and has, as all Brandon Sanderson novels do, an interesting and unique magic system. Highly recommended.</p><p></p></li></ul><h1>Roadmap</h1><p>The roadmap is my way of committing to what I&#8217;m going to do before the next issue:</p><h4>16th March - 12th April</h4><ul><li><p>Complete the <em>Darkest Dungeon</em> app<em> &#9989;</em></p></li><li><p>Work on the <em>Board Game Lists</em> app &#9989;</p></li><li><p>Have <em>Music Library Tracker</em> v2.1 ready for submission to the App Store &#10060;</p></li><li><p>Build a Mac Catalyst app for sending commands to my receipt printer &#9989;</p></li></ul><p>I didn&#8217;t do a great deal on the <em>Board Game Lists </em>app but just barely enough for it to qualify for a green tick &#128517; - details on that coming next issue. <em>Music Library Tracker</em> v2.1 is not ready but I did get a v2.0.2 bug fix release out so that counts for something!</p><h4>13th April - 10th May (Issue #9)</h4><ul><li><p>Complete v2.1 of <em>Music Library Tracker</em></p></li><li><p>Finish the main list feature of <em>Board Game Lists</em></p></li></ul><p>That wraps it up for this issue. I hope you found something of interest and that you&#8217;ll be able to recommend the newsletter to your friends, family, and colleagues. You can always comment on this issue or email me directly via <a href="mailto:ben@bendodson.com">ben@bendodson.com</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/subscribe?"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/8-healthy-living/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/8-healthy-living/comments"><span>Leave a comment</span></a></p><p></p><p></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>To be fair, this was developed when SwiftUI was quite young and there weren&#8217;t any 3rd party graph frameworks, let alone the new <a href="https://developer.apple.com/documentation/charts">Swift Charts framework</a> from Apple.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Scales! Get it?</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>That and I really don&#8217;t want to run a marathon.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>&#127926; I don't care for sherry, one cannot drink stout, And port is a wine I can well do without, It's simply a case of <em>chacun &#224; son go&#251;t &#127926;</em></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>The Shortcuts app does not de-duplicate steps so if you use that you&#8217;ll get an inflated step count from the combination of both your iPhone and Apple Watch; my app de-duplicates the data before exposing it to Shortcuts.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>You can get a rough approximation from websites like <a href="https://howlongtobeat.com">howlongtobeat.com</a> and some games do show your percentage completion within their save screens but those aren&#8217;t nearly as accurate as &#8220;I&#8217;m on page 228 of 349&#8221; nor can they account for non-linear games that don&#8217;t have a fixed ending or games that may require multiple playthroughs.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p>Unfortunately there is a bug in that the pencil icon disappears from the rename button when you add additional buttons to the list. If anybody knows how to fix this, <a href="https://mastodon.bendodson.com/@ben/110185247184061892">let me know</a>!</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-8" href="#footnote-anchor-8" class="footnote-number" contenteditable="false" target="_self">8</a><div class="footnote-content"><p>The code &#8220;works&#8221; in the sense that if all the stars align it&#8217;ll do what it&#8217;s supposed to. There is no error handling though so you&#8217;re on your own if nothing happens &#129315;</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-9" href="#footnote-anchor-9" class="footnote-number" contenteditable="false" target="_self">9</a><div class="footnote-content"><p>I go into this in more depth in the <a href="https://www.thedododeveloper.com/i/108383997/focus-modes-and-shortcuts">last issue</a> but I&#8217;m running a specific shortcut in the morning to sync up all the data from my iPhone to my database in order to power the <em>Stoutness</em> app.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-10" href="#footnote-anchor-10" class="footnote-number" contenteditable="false" target="_self">10</a><div class="footnote-content"><p><a href="https://www.amazon.co.uk/Security-eufyCam-Wireless-Requires-HomeBase/dp/B07XCC3GDX/ref=sr_1_3?tag=bendodsoncom-21">Additional cameras are available</a> for &#163;80 each (currently &#163;70 on Amazon UK with a &#163;10 discount voucher)</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-11" href="#footnote-anchor-11" class="footnote-number" contenteditable="false" target="_self">11</a><div class="footnote-content"><p>I always hate the expression &#8220;feel free&#8221;. &#8220;Hey, feel free to do this thing that benefits me&#8221; - it&#8217;s weird, but people do it so I should use it more &#128514;</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-12" href="#footnote-anchor-12" class="footnote-number" contenteditable="false" target="_self">12</a><div class="footnote-content"><p><em>&#8220;The girl wondered why men so often used food to describe women&#8217;s features. There was a hunger to such men that was best avoided.&#8221;</em></p><p></p></div></div>]]></content:encoded></item><item><title><![CDATA[#7: The Postmortem]]></title><description><![CDATA[How much money does an app in the Top 10 of all paid apps on the App Store actually make?]]></description><link>https://www.thedododeveloper.com/p/7-the-postmortem</link><guid isPermaLink="false">https://www.thedododeveloper.com/p/7-the-postmortem</guid><dc:creator><![CDATA[Ben Dodson]]></dc:creator><pubDate>Wed, 15 Mar 2023 15:15:45 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/871f1d5f-d2b4-4423-b153-bd6f8a2e94bb_1240x829.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dear Reader,</p><p>You join me one month after the launch of <em>Music Library Tracker v2.0</em> and I hopefully have an interesting read for you in the form of a retrospective of the post-launch period. It details how much the app made in the past 4 weeks along with my strategy for media promotion and how that went. I&#8217;d love to hear your thoughts, especially if you&#8217;re an iOS developer that has had the same struggles with self-promotion.</p><p>That aside, I received an email about my <a href="https://dodoapps.io/return-to-dark-tower-assistant/">Return to Dark Tower Assistant app</a> that absolutely made my day. I originally said when I published the app that &#8220;it will likely only get single-digit downloads&#8221; and was pleasantly surprised when it ended up comfortably in the double-digits &#128518;. The app only received one review on the App Store and unfortunately it got 3 out of 5 stars as &#8220;the expansion characters are not available to select&#8221;. I did not have a copy of the Alliances expansion so I posted a reply essentially saying that I couldn&#8217;t add the expansion content until I received it which wouldn&#8217;t be until June or July this year<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>&#8230; that was unless some kind person sent me photos of all the cards.</p><p>I literally could not believe it when someone named Michelle did exactly that and sent me high resolution photos of all of the components as they had &#8220;noticed that someone left you a middle star review because you don&#8217;t have the alliance cards yet&#8221;.</p><p>A few things about this really cheered me:</p><ol><li><p>I love getting emails about any of my apps but I really love it when my niche apps get some attention.</p></li><li><p>People actually read the App Store reviews and their replies! Rather than being turned away by a middling review they downloaded the app anyway and then helped make it better.</p></li><li><p>It&#8217;s just a lovely thing to do and restored my faith in humanity &#128519;</p></li></ol><p>It took a little while to get everything programmed in (as there is a tricky new mechanic around Guilds) but yesterday v1.1 of <a href="https://apps.apple.com/us/app/return-to-dark-tower-assistant/id1659406816">Return to Dark Tower Assistant</a> was approved and made available on the App Store with full support for the Alliances expansion including the option to use the 2 new heroes with the base game.</p><p>Again, I know the vast majority of you will have no interest in that app but hopefully the takeaway from this story is that you should go and support the creators you like. You don&#8217;t need to spend money or loudly promote something on social media (although those are both gratefully received); a small kindness can go a long way to improving someone&#8217;s wellbeing and keep them going. Thank you Michelle &#128591;&#127995;</p><p><strong>&#8212; Ben</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/7-the-postmortem/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/7-the-postmortem/comments"><span>Leave a comment</span></a></p><h1>Contents</h1><ul><li><p>Music Library Tracker v2.0 Postmortem</p></li><li><p>Focus Modes &amp; Shortcuts</p></li><li><p>Darkest Dungeon Board Game App</p></li><li><p>Dolby Audio</p></li><li><p>Recommended Links</p></li><li><p>Roadmap</p></li></ul><h1>Music Library Tracker v2.0 Postmortem</h1><p>It&#8217;s been 4 weeks since the v2.0 update for <a href="https://dodoapps.io/music-library-tracker/">Music Library Tracker</a> launched so I thought now was a good time for a retrospective to detail how I promoted the app and how well it performed.</p><p>By way of a bit of background, the app originally launched back in January 2016 at a $0.99 price point making $1368 in it&#8217;s first month before dropping off significantly to roughly $20 a month. In January 2021, I was accepted into the <a href="https://developer.apple.com/app-store/small-business-program/">App Store Small Business Program</a> which meant the amount Apple took from sales fell from 30% to 15%; I had also increased the price and released a few more updates so the average profit for the half year prior to the v2.0 update in February was sitting at around $80 a month<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>. This is by no means an income (especially as I have to pay corporation tax on it in the UK and then if I want to actually take the money for myself rather than my business I&#8217;ll have to pay some more tax) but it was fine for an app that didn&#8217;t have any running costs nor require much maintenance.</p><p>v2.0 was released on 13th February 2023 after a 9 month development period, 3 months of which was open development in this very newsletter. It was reported on by a couple of tech sites (I&#8217;ll detail how shortly) and ended up being the #8 Paid app in the US!</p><p>So how much money does an app need to make to be in the Top 10 of all paid apps on the App Store?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cc-E!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfce370c-3203-40aa-b68d-7d5e46376055_1600x722.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cc-E!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfce370c-3203-40aa-b68d-7d5e46376055_1600x722.png 424w, https://substackcdn.com/image/fetch/$s_!cc-E!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfce370c-3203-40aa-b68d-7d5e46376055_1600x722.png 848w, https://substackcdn.com/image/fetch/$s_!cc-E!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfce370c-3203-40aa-b68d-7d5e46376055_1600x722.png 1272w, https://substackcdn.com/image/fetch/$s_!cc-E!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfce370c-3203-40aa-b68d-7d5e46376055_1600x722.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cc-E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfce370c-3203-40aa-b68d-7d5e46376055_1600x722.png" width="1456" height="657" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bfce370c-3203-40aa-b68d-7d5e46376055_1600x722.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:657,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:42704,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cc-E!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfce370c-3203-40aa-b68d-7d5e46376055_1600x722.png 424w, https://substackcdn.com/image/fetch/$s_!cc-E!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfce370c-3203-40aa-b68d-7d5e46376055_1600x722.png 848w, https://substackcdn.com/image/fetch/$s_!cc-E!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfce370c-3203-40aa-b68d-7d5e46376055_1600x722.png 1272w, https://substackcdn.com/image/fetch/$s_!cc-E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfce370c-3203-40aa-b68d-7d5e46376055_1600x722.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Daily profit in USD over the past 28 days peaking at $1534 on February 15th</figcaption></figure></div><p>Not as much as you might think! You can <a href="https://bendodson.s3-eu-west-1.amazonaws.com/weblog/2023/music-library-tracker-v2-sales.csv">download a full breakdown</a> but the key figures are:</p><ul><li><p>Profit of $82 on 13th Feb (launch day), $1449 on 14th Feb, $1534 on 15th Feb, and $414 on 16th Feb</p></li><li><p>Total profit of $5351 over 28 days</p></li><li><p>An average daily profit of $191</p></li><li><p>Only a single sale on March 6th &#128557;</p></li></ul><p>I use <a href="https://www.dailysalesemail.com/">Daily Sales Email</a> to find out how much I&#8217;ve made each day but the figures typically arrive around lunchtime on the following day. That meant I could see the app in the Top 10 of all paid apps but had no idea what that would translate into<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> until the next day. I&#8217;ll confess that whilst I was pleased with the numbers, I was a little disappointed that I&#8217;d made less than what I charge for working 2 days as a freelance iOS developer.</p><p>That said, the app has settled down into making roughly $40 per day which works out at around $1200 per month, not bad for something that will hopefully only need minor maintenance.</p><p>With the financial breakdown out of the way, I thought it might be interesting to detail exactly how I promoted the app. I will be completely honest and say it is not my strong suit at all. I hate doing app promotion work; it is abhorrent to me. I&#8217;m not sure if it&#8217;s the Englishman in me or something else but I absolutely hate having to email people saying &#8220;please look at my app&#8221; followed by the waiting and hoping that somebody will feature it. However, that&#8217;s what I had to do as an app of this nature likely isn&#8217;t going to generate enough revenue to make hiring a marketing person cost effective.</p><h3>Reviews</h3><p>The key thing for an app like this is for it to be written about by a tech site. I&#8217;ve had a couple articles in the past from sites like <a href="https://9to5mac.com/2016/03/15/music-tracker-large-libraries/">9to5mac</a> and <a href="https://www.macrumors.com/2016/01/20/music-tracker-apple-music-library-monitoring/">MacRumors</a> so my first port of call was to send them an email. As previously mentioned, I hate doing this stuff but I felt on slightly firmer ground with these sites as they&#8217;d written about the app before so that seemed like a good &#8220;in&#8221;:</p><blockquote><p>Hello,</p><p>Back in 2016 you were kind enough to review an app of mine, Music Library Tracker (https://9to5mac.com/2016/03/15/music-tracker-large-libraries/).</p><p>I&#8217;m getting in touch as I&#8217;ve just released a large v2.0 update to the app which includes some features around Spatial Audio. In short, the app can quickly scan your library and show you exactly which songs have been upgraded to Spatial Audio and generate a playlist containing just those tracks; it will then run in the background periodically and notify you as and when tracks are upgraded and keep that playlist up to date.</p><p>This is all possible due to a database of Dolby Atmos and Dolby Audio tracks I&#8217;ve created over the past 9 months to run my Spatial Audio Finder website (https://bendodson.com/projects/spatial-audio-finder/) and the @NewSpatialAudio Twitter account (https://twitter.com/NewSpatialAudio) which tweets whenever a new track is upgraded. This database is sourced from a minor update to the Apple Music API at WWDC 22 - you can see how this all works in a blog post I wrote last year (https://bendodson.com/weblog/2022/06/27/spatial-audio-finder/) but suffice to say I do not believe there is anyone outside of Apple with a dataset such as this.</p><p>Apple Music does not yet have a clear strategy for displaying Spatial Audio tracks. Whilst they have some playlists and collections that get updated weekly, the only way to tell which tracks in your own library are upgraded is to play them and see. This is obviously not ideal and not a great way to showcase what is a genuine leap in musical quality and the hundreds of thousands of tracks that have been upgraded. I created this feature as I was determined to find a way to see which tracks had been updated. From the response I&#8217;ve received via @NewSpatialAudio it seems I&#8217;m not alone!</p><p>The app is still a single cost download (25% off for the next week) with no in-app purchases, subscriptions, or adverts so anybody who downloaded the app in the past 7 years will get this new feature for free. I&#8217;ve provided a few promo codes below in case you or anyone at the MacRumors team are interested in taking a look:</p><p>CODE1<br>CODE2<br>CODE3</p><p>You can see some more information about the app at https://dodoapps.io/music-library-tracker/ and there is a full media kit with screenshots, etc, at https://dodoapps.io/music-library-tracker/media-kit/</p><p>The update is available now on the App Store at https://apple.co/3XtdAga</p><p>If you have any questions at all about the app, my Spatial Audio database, or anything else relating to Spatial Audio then just let me know.</p><p>All the best,</p><p>Ben</p></blockquote><p>I sent this email on the 13th February to the reviews@9to5mac.com address (as my previous contact had since moved elsewhere) and a very similar version with a different link directly to the Senior Editor at MacRumors who wrote a previous article. I got a <a href="https://bendodson.s3-eu-west-1.amazonaws.com/weblog/2023/9to5mac-bounceback.png">very strange bounceback email</a> from 9to5mac and I didn&#8217;t get a reply at all from MacRumors. As the bounceback was so odd, I waited a day and then sent a follow up email to tips@9to5mac.com; it was a good thing I did as Chance Miller got in touch within 30 minutes and shortly afterwards there was an <a href="https://9to5mac.com/2023/02/14/apple-music-spatial-audio-songs/">article published</a>. This is undoubtedly what led to the spike in sales on the 14th and afterwards.</p><p>In addition to those two outlets, I sent similar emails to:</p><ul><li><p>TechRadar (via news@techradar.com): their Entertainment Editor replied after a couple of hours and we had a few emails back and forth discussing various aspects of Spatial Audio such as how Apple bans AI upscaling. They <a href="https://www.techradar.com/opinion/finding-dolby-atmos-tracks-on-apple-music-is-hell-this-app-fixes-that">published an article</a> the next day but it didn&#8217;t show up in their RSS feed and is not visible on their website unless you follow the direct link (which was tweeted but had practically zero engagement)</p></li><li><p>AppleInsider (direct to one of their writers who had written a recent article about Spatial Audio): I didn&#8217;t get a reply and there hasn&#8217;t been an article</p></li><li><p>MacStories (direct email to John and Federico): I didn&#8217;t get a reply but it was listed in the &#8220;App Debuts&#8221; section of the Club MacStories newsletter for paid subscribers</p></li></ul><p>The following week I sent an email to iMore as I&#8217;d noticed an <a href="https://www.imore.com/music-movies-tv/apple-music/apple-is-showcasing-non-spatial-audio-albums-in-the-spatial-audio-page">interesting article</a> relating to Spatial Audio. I couldn&#8217;t find an email address for the author, Tammy Rogers, so instead sent an email direct to the Features Editor, Daryl Baxter, who was listed as a contributor:</p><blockquote><p>Hi Daryl,</p><p>I came across a recent article you contributed to, &#8220;Apple Music is showcasing non-Spatial Audio albums in it&#8217;s Spatial Audio page&#8221;, and had two things that may be of interest to you and Tammy (I couldn&#8217;t find an email address for her so my apologies for not including her as well).</p><p>First of all, the reason that those albums are being listed within Apple Music&#8217;s Spatial Audio playlists is because they have some tracks on them that are available in Spatial Audio. The referenced No Pressure by Logic has two tracks that have been upgraded (GP4 and Perfect) whilst McCartney (2011) remaster has the first 13 tracks available in Spatial Audio. I know this because I created something called the Spatial Audio Finder which lets you find which tracks have been updated for a particular artist (I&#8217;ve got a blog post at https://bendodson.com/weblog/2022/06/27/spatial-audio-finder/ which explains how that all works). I also publish when tracks are upgraded to the @NewSpatialAudio Twitter feed.</p><p>You also mentioned in the article that it&#8217;s quite hard to find Spatial Audio tracks within Apple Music. This is a huge bugbear of mine and so I recently updated an app of mine, Music Library Tracker, with some new features around Spatial Audio. The app was originally designed to help notify you when Apple changes your music (i.e. if a song is deleted due to licensing changes, etc) but it can now scan your library and show you which tracks you have that are available in Spatial Audio along with creating a playlist in Apple Music containing only those tracks. It can then keep monitoring your library and send you notifications as and when new tracks are updated.</p><p><em>The rest of the email is similar to the initial one above</em></p></blockquote><p>I received a reply a few days later and then after 2 weeks <a href="https://www.imore.com/music-movies-tv/apple-music/this-app-makes-finding-your-apple-music-spatial-audio-tracks-easier-than-ever">an article appeared</a>.</p><p>In addition to the sites I reached out to, a few sites published articles organically including:</p><ul><li><p><a href="https://appadvice.com/post/music-library-tracker-is-perfect-for-fans-of-spatial-audio-tracks-on-apple-music/776748">AppAdvice</a></p></li><li><p><a href="https://www.whathifi.com/news/you-can-now-find-out-when-apple-music-songs-get-spatial-audio-fied">What Hi-Fi</a></p></li><li><p><a href="https://www.tomsguide.com/news/how-to-discover-spatial-audio-tracks-in-your-apple-music-library">Tom&#8217;s Guide</a></p></li></ul><p>I&#8217;d like to give a big thank you to all of the people who did get back to me or wrote about the app - I&#8217;m very grateful! However, the experience of doing this is easily the worst part of being an independent app developer. I absolutely hate having to hawk the app around and then have the long period of waiting and hoping for an article to appear. I always try and craft my emails to be very specific to something the site has covered before or to provide some kind of story so it&#8217;s a bit easier to form a narrative other than &#8220;please talk about my app&#8221;. It&#8217;s incredibly disappointing when you don&#8217;t even get an email back. As I hated doing it, I&#8217;d typically send an email and then think &#8220;that&#8217;ll do&#8221; and by the time I realised a site wasn&#8217;t going to pick it up the launch window had passed and it felt even more awkward to email in (especially as it had already been covered by 9to5mac so other sites could have potentially already seen that article and not wanted to cover something which is now old news).</p><p>A few things I should have done differently:</p><ol><li><p>I should have contacted people before the launch of the app rather than afterward. I don&#8217;t like contacting anyone before Apple have approved an app as that can lead to all sorts of problems. I&#8217;d already public committed to a release date and didn&#8217;t give myself much room between approval and release so I just sent the messages out post-launch. In an ideal world, I should have had a week or even two with the app approved within which I could have sent out promo codes or TestFlight invites so the app could be reviewed and embargoed. That would lead to a much bigger &#8220;splash&#8221; and also avoids the issue of sites potentially not wanting to promote an app that has already been promoted elsewhere.</p></li><li><p>I should have written to more sites rather than just the ones I typically read. I did do some research to find sites that had talked about Spatial Audio (as I wanted some kind of an &#8220;in&#8221; when writing to someone who&#8217;d never heard of me before) but I probably should have just gone with a scattergun approach to anybody that is even vaguely app adjacent.</p></li><li><p>I had no idea if the promo codes I was sending out were being used so couldn&#8217;t really tell if my emails were getting through. Once you&#8217;ve generated a promo code within App Store Connect, the only way to see if it has been redeemed or not is to try and redeem it (which is obviously not a good idea). I could easily just provide a link to my site which, when accessed, gives out a promo code and can then tell me that has happened but it just doesn&#8217;t sit right with me and I&#8217;d be afraid it would be something that would put people off.</p></li><li><p>I should have followed up with the sites that didn&#8217;t reply to me. I did that with 9to5mac which definitely paid off but I felt more comfortable doing that as it seemed clear there was a technical error; sending a &#8220;sorry but did you get my email?&#8221; shouldn&#8217;t really be anxiety inducing but I couldn&#8217;t bring myself to do it.</p></li></ol><p>If you are a writer for a tech site with any insight or a developer that has had any success stories with this then I&#8217;d absolutely love to hear from you!</p><h3>Getting Featured on the App Store Form</h3><p>When you&#8217;re looking at ways to promote an app, getting featured by Apple on the App Store is obviously a high priority goal. There have been <a href="https://nemecek.be/blog/169/how-to-get-featured-on-the-app-store">several articles recently</a> about using the <a href="https://developer.apple.com/contact/app-store/promote/">dedicated form</a> on the Apple Developer website with the key takeaway being to submit the form for every app update.</p><p>I have never used this form before, mostly because my apps tend to either be very niche or are something like this app which I&#8217;m always somewhat surprised makes it through App Review in one piece &#128518;. However I did it use it and something unexpected happened&#8230; I got an email from the Apple Services Performance Partnership Team<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>:</p><blockquote><p>We are currently recruiting new partners to promote the latest of Apple&#8217;s products to join the programme: Apple MusicKit.</p><p>MusicKit lets users play Apple Music and their local music library from your app or website. So, when your users provide permission to access their Apple Music account, they can use your app or website to create playlists, add songs to their library, and play any of the millions of songs in the Apple Music catalog! If your app detects that the user is not yet an Apple Music member, you can offer a trial membership from within your app. The Apple Music Affiliate Program allows you to earn a commission on eligible referred Music memberships (new sign-ups only)! You can find more detailed information here as well as in the document attached.</p><p>We have noticed that you already use the Apple Music API and we believe adding in MusicKit would be an easy process for you and a great benefit! We offer generous compensation models and would like to talk you through this opportunity in more detail.</p><p>Please let us know your avails, so we can go ahead and schedule a call with you. &#128522;</p></blockquote><p>I did take the call<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a> and it is effectively outreach to try and get developers to promote Apple Music within their apps in exchange for a commission on any new subscriptions. You can already <a href="https://partners.applemediaservices.com/other-partners">apply for this directly</a> but I guess Apple saw that I was using MusicKit on the form I filled out and so set this up. Unfortunately it&#8217;s not really a good fit for this app (you&#8217;re likely not using it if you don&#8217;t have Apple Music already) but it may be useful for another app I have in the pipeline in which I&#8217;d already added the <a href="https://developer.apple.com/documentation/storekit/skcloudservicesetupviewcontroller/offering_apple_music_subscription_in_your_app">&#8220;Subscribe to Apple Music&#8221; interstitial</a> that this hooks into.</p><p>Going back to the form, the app has not been featured anywhere on the App Store but I had very little expectation of that happening.</p><h3>App Store Ads</h3><p>I took a look at promoting the app using Apple Search Ads and found that it was recommending a suggested &#8220;Cost-per-Install&#8221; of &#163;5.61. This is not ideal bearing in mind the app cost &#163;2.49 at the time &#129315;</p><p>After I posted that on Twitter the developer of the excellent <a href="https://apps.apple.com/us/app/marvis-pro/id1447768809">Marvis Pro</a> music app, Aditya Rajveer, <a href="https://twitter.com/AdityaRajveer/status/1625523289321005057">reached out</a> and said &#8220;It almost never reached the suggested amount per install for my app, not even close&#8221;. That pushed me to give it a try and they were right! I&#8217;ve had it running for a few weeks now and have had 22 installs on an average Cost-Per-Install of &#163;0.89. That&#8217;s not exactly setting my sales alight but it&#8217;s better than nothing. On a more positive note, I&#8217;m not actually being charged for these installs as I have a promotional balance apparently. I seem to remember I claimed a free $100 of advertising years and years ago so evidently that is still in use &#129335;&#127995;&#8205;&#9794;</p><h3>App Store In-App Events</h3><p>I created an In-App Event on the App Store to coincide with the release of the update which ran for 1 week:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mXK8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44999ca1-3a89-4f0e-a305-7eeed0e78de9_1290x1096.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mXK8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44999ca1-3a89-4f0e-a305-7eeed0e78de9_1290x1096.jpeg 424w, https://substackcdn.com/image/fetch/$s_!mXK8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44999ca1-3a89-4f0e-a305-7eeed0e78de9_1290x1096.jpeg 848w, https://substackcdn.com/image/fetch/$s_!mXK8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44999ca1-3a89-4f0e-a305-7eeed0e78de9_1290x1096.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!mXK8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44999ca1-3a89-4f0e-a305-7eeed0e78de9_1290x1096.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mXK8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44999ca1-3a89-4f0e-a305-7eeed0e78de9_1290x1096.jpeg" width="498" height="423.10697674418606" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/44999ca1-3a89-4f0e-a305-7eeed0e78de9_1290x1096.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1096,&quot;width&quot;:1290,&quot;resizeWidth&quot;:498,&quot;bytes&quot;:573981,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!mXK8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44999ca1-3a89-4f0e-a305-7eeed0e78de9_1290x1096.jpeg 424w, https://substackcdn.com/image/fetch/$s_!mXK8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44999ca1-3a89-4f0e-a305-7eeed0e78de9_1290x1096.jpeg 848w, https://substackcdn.com/image/fetch/$s_!mXK8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44999ca1-3a89-4f0e-a305-7eeed0e78de9_1290x1096.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!mXK8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44999ca1-3a89-4f0e-a305-7eeed0e78de9_1290x1096.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The irony is that you can't listen to Spatial Audio on those headphones but it was the only decent royalty-free image I could find...</figcaption></figure></div><p>This had 4700 impressions leading to 9 downloads and 24 app opens. Again, not terribly exciting but extra sales are extra sales.</p><h3>Other Promotions</h3><p>I obviously promoted the app on my own <a href="https://mastodon.bendodson.com/@ben">Mastodon</a> and <a href="https://twitter.com/bendodson/">Twitter accounts</a> but I also tweeted about it on the <a href="https://twitter.com/newspatialaudio">@NewSpatialAudio</a> account which I believe led to the article on Tom&#8217;s Guide. There&#8217;s also <a href="https://www.thedododeveloper.com/">my newsletter</a> (this one you&#8217;re reading!) and <a href="https://bendodson.com/">my website</a> which mentioned the app. Finally, it was mentioned in both the <a href="https://indiedevmonday.com/issue-134">Indie Dev Monday</a> and <a href="https://swiftlyrush.curated.co/issues/62">SwiftlyRush</a> newsletters.</p><h3>So what actually worked?</h3><p>App Store Connect provides a metrics panel which roughly details where your downloads have come from. Rather astonishingly, it turns out that 43.4% of all my downloads in the past month came from &#8220;App Store Browse&#8221;. This is followed by &#8220;Web Referer&#8221; at 28.3%, &#8220;App Store Search&#8221; at 13.4%, and &#8220;App Referer&#8221; at 12.8%.</p><p>If I dig into that a little more I can see that most of the app referer traffic was either Facebook, Google, or Google Chrome (so likely clicking on links from one of the published articles). With web referer, the vast majority is 9to5mac.com followed by my own <a href="https://dodoapps.io/music-library-tracker/">Dodo Apps website</a>. Everything else is single digits.</p><p>My assumption is that the 9to5mac article created enough downloads to catapult the app up the Paid App charts and it was there that it was discovered by those just browsing the App Store who then made up the majority of my sales. This seems incredibly backwards to me as I&#8217;d assume the technical readership for whom this app is more likely aimed at would be the majority of downloaders but I suspect that with the billions of iOS devices in the world even a fractional percentage of users browsing the App Store is going to be magnitudes larger than the number of followers that the tech sites have.</p><p>In terms of next steps, I&#8217;m at a slight loss as to what to do as I don&#8217;t have any big splashy features in the pipeline that would merit the coverage that is clearly key to increasing the number of downloads. Having looked at what other developers are doing, it looks like I should try finding an influencer on TikTok but I know absolutely nothing about that world. I could also look at direct advertising on some of the tech sites or podcasts that would be relevant but doing so is likely going to be thousands of pounds worth of investment and feels like a bit of a gamble given this is a low-cost paid app rather than a subscription based service that can recoup large advertising costs over months of later usage.</p><p>If you&#8217;ve got any thoughts or insights then I&#8217;d <a href="https://bendodson.com/contact/">love to hear from you</a>. I&#8217;d also love it if you <a href="https://itunes.apple.com/us/app/music-tracker-automatic-tracking/id1062583712?ls=1&amp;mt=8">downloaded the app</a> &#128521;</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://apps.apple.com/gb/app/music-library-tracker/id1062583712&quot;,&quot;text&quot;:&quot;Download Music Library Tracker&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://apps.apple.com/gb/app/music-library-tracker/id1062583712"><span>Download Music Library Tracker</span></a></p><p></p><h1>Focus Modes &amp; Shortcuts</h1><p>The Shortcuts app is one of those things I always feel like I should use more but never seem to have the use case. This is partly because, as a programmer, I can usually join things together outside of the app ecosystem. Similarly I&#8217;ve not really delved into focus modes as I&#8217;ve not had the need. This changed on my recent holiday where I decided to create a &#8220;Family Time&#8221; focus mode and I&#8217;ve found I can now solve a few problems with both features.</p><p>It all goes back to an Apple TV app I built for myself called <em>Stoutness</em><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a> which was designed as a morning routine app that would show me my various vitals and trends whilst I did my chiropractic stretches. I won't go into great detail about it here (as this issue is quite long enough and there's a <a href="https://bendodson.com/weblog/2021/03/10/stoutness/">post on my website</a> that shows it off) but the important part is that I have data on my iPhone that needs to get sent to my server each morning; things like how many steps I walked, how much water I drank, how long I slept for, and so on. This is all done using a shortcut called &#8220;Update Logs&#8221; but the problem is that I have to run it every morning and sometimes I forget. You may think &#8220;just set it to run on a schedule&#8221; but as I&#8217;m accessing data from HealthKit the phone has to be unlocked; any shortcut that runs whilst the phone isn&#8217;t in use will fail.</p><p>A potential solution to this came to me whilst reading <a href="https://club.macstories.net/posts/my-day-and-time-based-focus-modes">an article</a> by John Voorhees and I noticed that there was a &#8220;when I wake up&#8221; trigger within Shortcuts. My plan was to use this to run my shortcut as I always turn the Sleep focus mode off on my phone which meant it would be unlocked and thus there would be no issues with Health access.</p><p>It turns out there were two problems with this:</p><ol><li><p>Whilst you can activate shortcut triggers when focus modes are enabled or disabled, for some reason this does not extend to the Sleep focus mode. There are triggers for &#8220;Sleep&#8221; that include &#8220;wind down begins&#8221;, &#8220;bedtime begins&#8221; and &#8220;waking up&#8221; but these only work if you&#8217;re using the full bedtime functionality. I don&#8217;t use this as a) I track my sleep with <a href="https://autosleepapp.tantsissa.com">AutoSleep</a> rather than the native Apple Watch tracking, b) I don&#8217;t have a consistent bedtime, and c) my children act as an alarm clock and they are unpredictable with their timings.</p></li><li><p>Even if I were to run my shortcut straight after waking, this sometimes led to the AutoSleep shortcut integration returning 0 for my sleeping hours as it hadn&#8217;t yet had time to process that I&#8217;d woken up.</p></li></ol><p>To solve the first problem, I removed the Sleep focus mode entirely and created a new one named &#8220;Bedtime&#8221;. I set it up so that when this is activated my iPhone wallpaper is turned to black and Cinema Mode is enabled on my Apple Watch<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a>. I then reverse this when Bedtime is deactivated and rather than having my shortcut run at that point I instead set the automation to open the AutoSleep app; I then have an automation for when AutoSleep is closed which runs my shortcut.</p><p>This works great as now when I wake up I turn off the Bedtime focus mode which immediately opens AutoSleep,  I refresh the app so the data is all up to date, and then I swipe back to the home screen at which point my shortcut runs in the Dynamic Island and sends everything to my server. Perfect!</p><p>As I mentioned earlier, I&#8217;ve been experimenting with a &#8220;Family Time&#8221; focus mode that effectively turns off notifications for everything I shouldn&#8217;t be dealing with at the weekend and switches to the Mickey watch face<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-8" href="#footnote-8" target="_self">8</a>. I originally set this to run on a schedule which used to work fine but once I removed the Sleep focus mode the schedule started overwriting my Bedtime focus mode. It seems that Sleep has a few special edge cases of which this is one. I typically get a lie-in on Sunday so wasn&#8217;t too keen on my Bedtime focus being turned off at 6am &#128514;</p><p>To resolve this, I needed to use some calendar trickery in my previous automation that runs when Bedtime is disabled. Essentially it formats the current date into a string format like &#8220;Mon&#8221; or &#8220;Wed&#8221;, compares that against a list comprised of &#8220;Sat&#8221; and &#8220;Sun&#8221;, and then it sets the Family Time focus mode if that&#8217;s true (otherwise nothing happens so it reverts to no focus mode at all):</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!l_AR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7ed55c0-a96e-49a4-8ba9-34c8c6b59928_1450x2936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!l_AR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7ed55c0-a96e-49a4-8ba9-34c8c6b59928_1450x2936.png 424w, https://substackcdn.com/image/fetch/$s_!l_AR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7ed55c0-a96e-49a4-8ba9-34c8c6b59928_1450x2936.png 848w, https://substackcdn.com/image/fetch/$s_!l_AR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7ed55c0-a96e-49a4-8ba9-34c8c6b59928_1450x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!l_AR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7ed55c0-a96e-49a4-8ba9-34c8c6b59928_1450x2936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!l_AR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7ed55c0-a96e-49a4-8ba9-34c8c6b59928_1450x2936.png" width="424" height="858.5268965517241" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e7ed55c0-a96e-49a4-8ba9-34c8c6b59928_1450x2936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2936,&quot;width&quot;:1450,&quot;resizeWidth&quot;:424,&quot;bytes&quot;:2406401,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!l_AR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7ed55c0-a96e-49a4-8ba9-34c8c6b59928_1450x2936.png 424w, https://substackcdn.com/image/fetch/$s_!l_AR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7ed55c0-a96e-49a4-8ba9-34c8c6b59928_1450x2936.png 848w, https://substackcdn.com/image/fetch/$s_!l_AR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7ed55c0-a96e-49a4-8ba9-34c8c6b59928_1450x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!l_AR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7ed55c0-a96e-49a4-8ba9-34c8c6b59928_1450x2936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This now works automatically so if I turn off my Bedtime focus at the weekend I&#8217;ll automatically be piped into my Family Time focus mode.</p><p>One final focus mode I set up was &#8220;Photos&#8221;. This was inspired by a recent comment on <a href="https://www.relay.fm/connected">Connected</a> by Stephen Hackett and effectively activates a custom focus mode whilst the Photos app is active so people don&#8217;t see your alerts when you&#8217;re handing them your phone to show them photos, a common occurrence with small children. The issue here was that if it was the weekend I didn&#8217;t want my Family Time focus mode to be deactivated when I left the Photos focus mode. To fix this I needed a way to save state and the best way to do that in Shortcuts is with the <a href="https://datajar.app">Data Jar app</a>.</p><p>Data Jar exposes itself within shortcuts and lets you store and retrieve variable content. Think of it like <code>NSUserDefaults</code> in an app. With this, I&#8217;m able to save my current focus mode as a string into Data Jar when I open the Photos app and then when I close the Photos app I can check Data Jar to see if there is a string and if so use it to switch back to the correct focus mode.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!atDO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6dc4bb2d-cf1f-4548-b412-2ea6c9047376_2960x2936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!atDO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6dc4bb2d-cf1f-4548-b412-2ea6c9047376_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!atDO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6dc4bb2d-cf1f-4548-b412-2ea6c9047376_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!atDO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6dc4bb2d-cf1f-4548-b412-2ea6c9047376_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!atDO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6dc4bb2d-cf1f-4548-b412-2ea6c9047376_2960x2936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!atDO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6dc4bb2d-cf1f-4548-b412-2ea6c9047376_2960x2936.png" width="1456" height="1444" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6dc4bb2d-cf1f-4548-b412-2ea6c9047376_2960x2936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1444,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3192656,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!atDO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6dc4bb2d-cf1f-4548-b412-2ea6c9047376_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!atDO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6dc4bb2d-cf1f-4548-b412-2ea6c9047376_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!atDO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6dc4bb2d-cf1f-4548-b412-2ea6c9047376_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!atDO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6dc4bb2d-cf1f-4548-b412-2ea6c9047376_2960x2936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The &#8220;Save Current Focus&#8221; shortcut is nice and straightforward but the &#8220;Return to Last Focus&#8221; shortcut is a little cumbersome.</p><p>To start with, it is not possible to activate a focus mode using a variable. For example, I can&#8217;t use the string &#8220;Family Time&#8221; as an input to the &#8220;Set Focus&#8221; action; it lets you provide a variable but it will always just active Do Not Disturb. Hopefully this is just a bug that will be fixed at some point but for now I have to manually specify each focus mode I have which means this will break if I ever rename them &#129318;&#127995;&#8205;&#9794;</p><p>The second issue is a lack of control flow options in Shortcuts. Ideally you&#8217;d want to use a switch to loop through each possible string option and fallback to a default but there is no concept of a switch. There is also no concept of an if-else-if loop so you either need to do a lot of nesting (no thanks) or have a long list of &#8220;if value is x&#8221; commands which is what I ended up doing. It&#8217;s not pretty but it gets the job done.</p><p>I&#8217;ve only been using this for a couple of weeks but it&#8217;s working well so far and I suspect it will only get better as more options are added to Shortcuts in future!</p><p></p><h1>Darkest Dungeon</h1><p>In the <a href="https://www.thedododeveloper.com/p/6-launch-party">last issue</a> I said of my Darkest Dungeon app &#8220;<em>it&#8217;s going to be a fun diversion for me over the next couple of weeks, a palate cleanser before I dive back into some proper apps for release later in the year&#8221;</em>. I may have been wrong &#128514;</p><p>It wouldn&#8217;t be fair to say it&#8217;s been un-fun but it has taken a lot more work than I had initially planned and it&#8217;s an app that will never see the light of day on the App Store due to the amount of artwork I&#8217;ve used from the game.</p><p>As a very brief recap, <em>Darkest Dungeon</em> is a board game adaptation of the video game by the same name. It has a ton of components that make it quite cumbersome for solo play so I decided to make a companion app in a similar vein to my <a href="https://dodoapps.io/return-to-dark-tower-assistant/">Return to Dark Tower Assistant</a>. I have a deadline on this one as I&#8217;m due to start the campaign in-person with a good friend next week so I&#8217;d very much like this to be working!</p><p>For the purposes of this issue, I thought I&#8217;d run through a few things I&#8217;ve created in the app that I&#8217;m particularly pleased with. It&#8217;s a bit of a hodgepodge but hopefully there will be something of interest.</p><h3>SwiftUI and ObservableObjects  and Enums, oh my!</h3><p>To start with, I wanted to mention that this is another one of my experiments with SwiftUI. It&#8217;s an entirely native SwiftUI app currently designed only for the 12.9&#8221; iPad (as that&#8217;s the model we&#8217;re going to be using and gives me the largest canvas to work with). The thing that is slightly different with this app to my previous dabblings with SwiftUI is that I&#8217;m fully embracing <code>@ObservableObject</code>, a way to have a single source of truth that then filters down to any other composed views via <code>@EnvironmentObject</code>.</p><p>For example, I have a &#8220;game&#8221; object which contains a party property that is an array of my four adventurers. An adventurer has a multitude of properties for things like health, stance, and tokens. On the interface side I have a hero board which represents the currently active adventurer. With the game being an <code>@ObservableObject</code> it means that if the adventurers health changes then my hero board is automatically re-rendered. This is very cool especially when you&#8217;re using view compositions to break up a single SwiftUI view into individual components, each of which can easily respond when the data changes. In the UIKit world I would have had to have fired an <code>NSNotification</code> when something changed so that subviews could respond but this is much more straightforward.</p><p>Another thing that I&#8217;ve been dabbling with are enums with associated values. For example, here is my list of possible hero types within the game:</p><pre><code>enum Hero: Codable {
    case arbalest(level: Int)
    case bountyHunter(level: Int)
    case crusader(level: Int)
    case graveRobber(level: Int)
    case hellion(level: Int)
    case highwayman(level: Int)
    case jester(level: Int)
    case occultist(level: Int)
    case plagueDoctor(level: Int)
    case vestal(level: Int)

    var name: String {
        switch self {
        case .arbalest:
            return "Arbalest"
        case .bountyHunter:
            return "Bounty Hunter"
        case .crusader:
            return "Crusader"
        case .graveRobber:
            return "Grave Robber"
        case .hellion:
            return "Hellion"
        case .highwayman:
            return "Highwayman"
        case .jester:
            return "Jester"
        case .occultist:
            return "Occultist"
        case .plagueDoctor:
            return "Plague Doctor"
        case .vestal:
            return "Vestal"
        }
    }

    var level: Int {
        switch self {
        case .arbalest(let level), .bountyHunter(let level), .crusader(let level), .graveRobber(let level), .hellion(let level), .highwayman(let level), .jester(let level), .occultist(let level), .plagueDoctor(let level), .vestal(let level):
            return level
        }
    }
}

extension Hero: Equatable {
    static func == (lhs: Hero, rhs: Hero) -&gt; Bool {
        return lhs.name == rhs.name
    }
}</code></pre><p>The associated value of &#8220;level&#8221; means that I can specifically instantiate a lvl 2 crusader with <code>.crusader(level: 2)</code> but when I interrogate the value I can choose to ignore the level property entirely, for example when switching through all the cases to return the hero name. This is by no means a new feature in Swift but isn&#8217;t something I&#8217;ve really used before; now I can&#8217;t stop using it!</p><h3>Hero Board</h3><p>One of the key components I wanted to remove and have solely in the app is the hero board. There are four of these in the game (regardless of number of players) that comprise of a hero card onto which stacks of tokens can be placed, and a square tile which has spaces for skill cards and a stress tracker along the top:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!O2kB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc4dcf1-b19d-4d45-b23c-dceb09db290f_1910x1600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!O2kB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc4dcf1-b19d-4d45-b23c-dceb09db290f_1910x1600.png 424w, https://substackcdn.com/image/fetch/$s_!O2kB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc4dcf1-b19d-4d45-b23c-dceb09db290f_1910x1600.png 848w, https://substackcdn.com/image/fetch/$s_!O2kB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc4dcf1-b19d-4d45-b23c-dceb09db290f_1910x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!O2kB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc4dcf1-b19d-4d45-b23c-dceb09db290f_1910x1600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!O2kB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc4dcf1-b19d-4d45-b23c-dceb09db290f_1910x1600.png" width="1456" height="1220" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4cc4dcf1-b19d-4d45-b23c-dceb09db290f_1910x1600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1220,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4211042,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!O2kB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc4dcf1-b19d-4d45-b23c-dceb09db290f_1910x1600.png 424w, https://substackcdn.com/image/fetch/$s_!O2kB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc4dcf1-b19d-4d45-b23c-dceb09db290f_1910x1600.png 848w, https://substackcdn.com/image/fetch/$s_!O2kB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc4dcf1-b19d-4d45-b23c-dceb09db290f_1910x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!O2kB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc4dcf1-b19d-4d45-b23c-dceb09db290f_1910x1600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Here&#8217;s a screenshot taken from the board game demo on Tabletop Simulator</figcaption></figure></div><p>There is also a slot on the left for trinket cards, and slots on the right hand side for diseases and curios so you can see how this can fill a lot of space on the table when you have four of them.</p><p>My version is a lot more compact:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eiDk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff394dc17-4ab2-456c-b721-68644cef492a_652x1150.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eiDk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff394dc17-4ab2-456c-b721-68644cef492a_652x1150.png 424w, https://substackcdn.com/image/fetch/$s_!eiDk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff394dc17-4ab2-456c-b721-68644cef492a_652x1150.png 848w, https://substackcdn.com/image/fetch/$s_!eiDk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff394dc17-4ab2-456c-b721-68644cef492a_652x1150.png 1272w, https://substackcdn.com/image/fetch/$s_!eiDk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff394dc17-4ab2-456c-b721-68644cef492a_652x1150.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eiDk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff394dc17-4ab2-456c-b721-68644cef492a_652x1150.png" width="338" height="596.1656441717791" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f394dc17-4ab2-456c-b721-68644cef492a_652x1150.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1150,&quot;width&quot;:652,&quot;resizeWidth&quot;:338,&quot;bytes&quot;:642758,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!eiDk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff394dc17-4ab2-456c-b721-68644cef492a_652x1150.png 424w, https://substackcdn.com/image/fetch/$s_!eiDk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff394dc17-4ab2-456c-b721-68644cef492a_652x1150.png 848w, https://substackcdn.com/image/fetch/$s_!eiDk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff394dc17-4ab2-456c-b721-68644cef492a_652x1150.png 1272w, https://substackcdn.com/image/fetch/$s_!eiDk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff394dc17-4ab2-456c-b721-68644cef492a_652x1150.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A digital version of a much compacted hero board</figcaption></figure></div><p>The first thing I wanted to do was to create an exact replica of the hero card. This consisted of taking an <a href="https://bendodson.s3-eu-west-1.amazonaws.com/weblog/2023/HeroInManual.png">image from the manual</a> and using my photoshop skills to <a href="https://bendodson.s3-eu-west-1.amazonaws.com/weblog/2023/HeroBoardFrame.png">patch it up</a> and remove everything aside from the border and static elements. I then created a <a href="https://bendodson.s3-eu-west-1.amazonaws.com/weblog/2023/HeroBoardBackground.png">replica of the background</a> using artwork from within the <em>Darkest Dungeon</em> video game files to put behind the transparent frame. I also went through the video game files to find the exact artwork that was used for each character both for their avatar in the top left hand corner and their main body pose. Finally, I used <a href="https://www.myfonts.com/pages/whatthefont">WhatTheFont</a> to determine that they were using <a href="https://www.myfonts.com/collections/dwarven-axe-bb-font-blambot">Dwarven Axe</a>, picked out the correct colours from the manual with <a href="https://apps.apple.com/gb/app/system-color-picker/id1545870783?mt=12">Color Picker</a>, and overlaid the text on top.</p><p>It was only after doing all this that I remembered there was a demo of the board game on Tabletop Simulator and I could have just taken some <a href="https://bendodson.s3-eu-west-1.amazonaws.com/weblog/2023/HighResHero.jpg">high resolution artwork</a> out of that &#129318;&#127995;&#8205;&#9794;</p><p>The next thing to determine was how to display tokens on the card. In the game, the heroes are often afflicted by a variety of conditions each represented by tokens that are stacked together. At the start of their turn, you remove one token from each stack which can lead to an effect. For example, if I get afflicted by <em>2 bleed for 3 turns</em> that means I need to place a stack of three &#8220;2 Bleed&#8221; tokens; at the start of each turn, I&#8217;ll take one of those off the stack and take 2 damage to represent them slowly bleeding.</p><p>My original thought was to have the tokens animate in and be placed at slightly random degree angles so they&#8217;d look more realistic as a stack but after testing it I quickly realised you couldn&#8217;t see how many items were in the stack. This is crucial information as knowing how many tokens are in the stack may change your decision as to whether you want to use an item to remove the stack or not. Instead I decided to take some inspiration from the iOS home screen and instead show a badge in the top right hand corner with the number of tokens in the stack.</p><p>The final thing to change was the stress tracker. In the original game this is placed at the top of the side board but the colours are such that it is very hard to see how many points of stress you have taken. I decided to put this at the bottom of the card instead and then have the stress tracker fill in rather than moving a single token. This makes it much clearer at a glance how much stress your hero has taken on.</p><p>You may notice that the &#8220;Hamlet Skill&#8221; text is missing from my version of the card. This is only used in a small section of the game so I display this in a different UI during that phase.</p><p>There are a few other items that can appear on the board:</p><ul><li><p>Skills: These are the main actions your hero takes in battle and will nearly always require you to roll a die to resolve them. For that reason, I have not digitised these but will keep them physically in front of the player. This is also for brevity as there are 100s of cards I&#8217;d need to type in; there are 8 for each hero that you can swap between quests and each one has three variants depending on their level.</p></li><li><p>Trinkets: These are randomly rewarded to you at certain interaction points so made a good candidate for digitisation but their effects are nearly entirely down to dice rolls that the app would not perform so these will also remain physically in front of the player.</p></li><li><p>Diseases, Quirks, Virtues, and Afflictions: Each of these cards are randomly provided from their respective decks and have some form of passive effect that changes things in the game. I have bought all of these into the app and they can interact with one another. For example, I can tell the app my hero needs a negative quirk and it may randomly assign &#8220;Fragile&#8221;. This has an effect whereby every time the hero takes damage, they take 1 additional damage. This is all handled automagically by the app so if I tell it to add 2 damage it will actually add 3 as it is aware of this rule. These effects can stack in particularly nasty ways into something I&#8217;ve coined &#8220;the tree of cascading s**t that has befallen you&#8221;.</p></li></ul><h3>The Light</h3><p>One of the key components of the game is the light tracker. Various actions will affect the level of light in a dungeon and the current brightness will have passive effects that are easy to forget about.</p><p>Rather than having a tracker, I decided to have a small torch in the top left hand corner of the screen which would show the current level along with the passive effect:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bFSZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc96274f-515a-44c0-bb30-1fa49041585e_600x148.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bFSZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc96274f-515a-44c0-bb30-1fa49041585e_600x148.gif 424w, https://substackcdn.com/image/fetch/$s_!bFSZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc96274f-515a-44c0-bb30-1fa49041585e_600x148.gif 848w, https://substackcdn.com/image/fetch/$s_!bFSZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc96274f-515a-44c0-bb30-1fa49041585e_600x148.gif 1272w, https://substackcdn.com/image/fetch/$s_!bFSZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc96274f-515a-44c0-bb30-1fa49041585e_600x148.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bFSZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc96274f-515a-44c0-bb30-1fa49041585e_600x148.gif" width="600" height="148" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cc96274f-515a-44c0-bb30-1fa49041585e_600x148.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:148,&quot;width&quot;:600,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:175731,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bFSZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc96274f-515a-44c0-bb30-1fa49041585e_600x148.gif 424w, https://substackcdn.com/image/fetch/$s_!bFSZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc96274f-515a-44c0-bb30-1fa49041585e_600x148.gif 848w, https://substackcdn.com/image/fetch/$s_!bFSZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc96274f-515a-44c0-bb30-1fa49041585e_600x148.gif 1272w, https://substackcdn.com/image/fetch/$s_!bFSZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc96274f-515a-44c0-bb30-1fa49041585e_600x148.gif 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>As the light goes down, I dim the glow around the torch and change it red. You may also notice that the number representing the current level also fades from a bright white at level 5 (the maximum brightness) to a faded grey.</p><p>Another minor detail is that the light is actually gently pulsating. You can&#8217;t really see it in the gif above but the surrounding light does randomly breathe in and out as if it were a burning torch. Unnecessary, but I like that it&#8217;s there &#128518;</p><h3>The Cascading Tree</h3><p>The most complex piece of this app has been dealing with the number of effects that can stack up and how the app will then process them. For example, when I say my hero has taken 2 damage from an enemy then that could lead to an extra piece of damage being applied due to the &#8220;Fragile&#8221; quirk as I mentioned earlier. However, I might also have the &#8220;Spotted Fever&#8221; disease which applies 1 blight for 2 turns every time I take damage so that needs to be added on as well. If I have the &#8220;Clumsy&#8221; quirk as well then that would apply a debuff token to me every time I take damage. If stress is applied, then I might need to test their resolve and roll a die to see if they then gain a virtue or an affliction; this would interrupt the cascade (as there may be more conditions to follow) so I&#8217;ll need to handle that. Another potential interruption would be if my life was at 0 and I took more damage so I&#8217;d need to roll the deathblow die to see if I survive or if the hero is done.</p><p>All that is to say that it wasn&#8217;t enough to simply build up a list of effects and then run through them one by one. I needed to continuously check every effect that takes place to see if that added yet more to the tree. I also needed to build in an interruption system so if something happened that required the players input then the cascade could be temporarily put on hold until that interruption was resolved. Finally, I needed a way to manipulate values but have them <em>not</em> apply the tree just in case something went wrong and we needed to reset something to a specific value without having all the ancillary effects take place.</p><p>This is mostly done but it is the area that I expect will collapse under the weight of actual play. For that reason I&#8217;ll be taking my MacBook Air and a lightning cable with me so I can tweak the app on the fly when we start playing &#128514;</p><div><hr></div><p>That is my Darkest Dungeon app so far. I still have quite a list of tasks to complete before next Thursday including:</p><ul><li><p>Completing the cascading tree</p></li><li><p>Adding custom UI for the various interruptions</p></li><li><p>Adding a UI to change the torch level</p></li><li><p>Creating the UI for the other phases of the game (so far I&#8217;ve been focussed on the battle interface as this is 90% of the game but I do need an interface for &#8220;The Hamlet&#8221; and the exploration phase)</p></li><li><p>Adding a way to change the stance of heroes and a way to replace a dead hero with a new one from the stagecoach</p></li><li><p>Adding in music and narration from the video game</p></li></ul><p>Lots to do, not much time to do it. I&#8217;ll let you know how it went next issue!</p><p></p><h1>Dolby Audio</h1><p><a href="https://www.thedododeveloper.com/p/6-launch-party">Last issue</a> I said &#8220;<em>It will likely be a few days until the existing tracks in the database are updated as new tracks take priority for my scanning engine; as of right now, there are just under 2 million tracks that have been added thanks to the new app update &#128517;</em>&#8221;. This was incredibly optimistic. It took nearly 3 weeks to clear the backlog in the scanning engine as the thousands of app downloads led to almost 12 million tracks being added to the database!</p><p>I&#8217;m happy to say that this has all cleared out now and there have been a number of Dolby Audio tracks published over the past two weeks! As always you can see what tracks have been upgraded via <a href="https://twitter.com/newspatialaudio">@NewSpatialAudio</a> or by using the <a href="https://dodoapps.io/music-library-tracker/">Music Library Tracker app</a>.</p><p></p><h1>Recommended Links</h1><h4>Video Games</h4><ul><li><p><em><a href="https://futurlab.co.uk/game/powerwash-simulator-2/">PowerWash Simulator</a> - </em>I&#8217;d heard about this game on a number of podcasts and avoided it as it sounded like the sort of thing I&#8217;d get addicted to. I was right to be cautious! It&#8217;s a very chill game in which you clean a variety of environments and can upgrade your equipment to deal with more stubborn dirt. I really enjoyed the skateboard park and playground levels and am now packing the Prime Vista PRO which has a very pleasing purr with the 15&#176; nozzle. It&#8217;s stolen 25 hours of my life and will no doubt steal a lot more given the free Tomb Raider and Final Fantasy VII DLC that have recently been released. Available on GamePass.</p></li><li><p><em><a href="https://ioi.dk/hitman">Hitman III</a> </em>- I really got into the first game in the reboot Hitman series but didn&#8217;t play too much of <em>Hitman II</em>. I&#8217;ve been meaning to try <em>Hitman III</em> for a while though if only for the second level which is a very <em>Knives Out</em> style murder mystery. I&#8217;ve only been through the level a few times but it&#8217;s definitely something I&#8217;m going to rinse and repeat many more times (I&#8217;d highly recommend <a href="https://youtu.be/5N4U46QOyeA">this video</a> by Game Maker&#8217;s Toolkit about the art of repetition within Hitman). Available on GamePass; a single download of the game also gives you all the content from the first two games in the trilogy.</p></li></ul><h4>Apps</h4><ul><li><p><em><a href="https://bandbreite.watch">Bandbreite</a> </em>- I love Apple Watch bands (in fact I picked up <a href="https://mastodon.bendodson.com/@ben/109988153648852911">two new ones</a> from the Spring collection that launched last week) and as you likely know I love keeping track of everything! Enter <em>Bandbreite</em>, the best app for detailing your band collection. I&#8217;ve been using it for a long time but a recent update which adds logging has meant I now use it every day. They also have a nice <a href="https://bendodson.s3-eu-west-1.amazonaws.com/weblog/2023/apple-watch-band-collection-march-2023.jpg">stats export</a> for showcasing a collection.</p></li></ul><h4>Music</h4><ul><li><p><em><a href="https://music.apple.com/gb/album/diamonds-dancefloors/1634875613">Diamonds &amp; Dancefloors</a> </em>by Ava Max - I&#8217;ve been a secret fan of Ava Max ever since I heard her in <em><a href="https://music.apple.com/gb/album/clap-your-hands-feat-ava-max/1265164269?i=1265164821">Clap Your Hands</a></em> on the <em>Forza Horizon 5</em> soundtrack. Ironically, given this issue&#8217;s game recommendation, I also usually listen to her albums whilst pressure washing the patio. In any case, this new album is excellent (and available in Spatial Audio &#128521;). Every track is catchy but I&#8217;m particularly fond of <em>Sleepwalker</em>, <em>Hold Up (Wait A Minute)</em>, and <em>Cold As Ice</em>.</p></li></ul><h4>TV Shows</h4><ul><li><p><em><a href="https://www.disneyplus.com/en-gb/series/what-we-do-in-the-shadows/40SsBvhCRbp1">What We Do in the Shadows</a></em> - A Taika Waititi / Jermaine Clement created mockumentary comedy series about a group of vampires that have shared a house together for centuries. One of those vampires is Matt Berry. Need I say more? Need. I. Say. More.</p></li></ul><p></p><h1>Roadmap</h1><p>The roadmap is my way of committing to what I&#8217;m going to do before the next issue:</p><h4>16th February - 15th March</h4><ul><li><p>Complete the <em>Darkest Dungeon</em> app<em> &#10060;</em></p></li><li><p>Get back into working on my <em>Board Game Lists</em> app &#10060;</p></li></ul><p>Oof, not ideal! The <em>Darkest Dungeon</em> app has taken a lot longer than I expected and some client work kept me away from jumping back into <em>Board Game Lists</em> but it&#8217;s definitely something I want to work on.</p><p>I&#8217;ve still got a lot of client bits to work on, a brief trip to Sweden, and the Easter holidays with my children so it&#8217;s going to be another case of &#8220;1 month until the next issue&#8221;:</p><h4>16th March - 12th April (Issue #8)</h4><ul><li><p>Complete the <em>Darkest Dungeon</em> app. For realsies.</p></li><li><p>Work on the <em>Board Game Lists</em> app</p></li><li><p>Have <em>Music Library Tracker</em> v2.1 ready for submission to the App Store</p></li><li><p>I&#8217;ve treated myself to something I&#8217;ve wanted for a long time&#8230; a receipt printer. I don&#8217;t know why but I love the idea of being able to print out reminders or widget type content on a little tearable piece of paper. My aim is to get to grips with the <a href="https://supportcommunity.zebra.com/s/article/ZPL-Command-Information-and-DetailsV2?language=en_US">ZPL language</a> the printer uses and build a basic Mac Catalyst app that can send some automated print commands.</p></li></ul><p>That wraps it up for this issue. I hope you found something of interest and that you&#8217;ll be able to recommend the newsletter to your friends, family, and colleagues. You can always comment on this issue or email me directly via <a href="mailto:ben@bendodson.com">ben@bendodson.com</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/subscribe?"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/7-the-postmortem/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/7-the-postmortem/comments"><span>Leave a comment</span></a></p><p></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>I ordered the Alliances expansion alongside the recently revealed Covenant expansion but unfortunately it&#8217;ll all ship together when that is released. At least I should be one of the first to get Covenant so it won&#8217;t take long to get it into the app!</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>You can <a href="https://bendodson.s3-eu-west-1.amazonaws.com/weblog/2023/music-library-tracker-v1-sales.csv">download</a> my historic monthly breakdown if you&#8217;re interested. With the change from a 70/30 split to an 85/15 split for the last 2 years, the actual amount I&#8217;ve given to Apple over the past 7 years has been around 26% leaving an average monthly profit of $59.83.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>I don&#8217;t use any analytics in my apps so I couldn&#8217;t see any realtime usage information.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>It definitely came as a result of submitting that form as the email was sent to my personal address which I&#8217;d used on the form, not my Apple Developer account email address.&nbsp;</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>I nearly didn&#8217;t as they inexplicably used Microsoft Teams &#129315;</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>After the &#8220;Stoutness Exercises&#8221; performed by Winnie-the-Pooh which I resemble when I do my chiropractic stretches.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p>This is as close as I can get to mimicking the Sleep focus mode currently although I should be able to turn off the Always On display on the iPhone 14 Pro when iOS 16.4 is released later this month.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-8" href="#footnote-anchor-8" class="footnote-number" contenteditable="false" target="_self">8</a><div class="footnote-content"><p>It&#8217;s been 8 years Apple. Why isn&#8217;t there a Donald Duck version yet &#128557;</p></div></div>]]></content:encoded></item><item><title><![CDATA[#6: Launch Party 🚀]]></title><description><![CDATA[Music Library Tracker v2.0 finally goes live... and then v2.0.1 goes live &#129318;&#127995;&#8205;&#9794;&#65039;]]></description><link>https://www.thedododeveloper.com/p/6-launch-party</link><guid isPermaLink="false">https://www.thedododeveloper.com/p/6-launch-party</guid><dc:creator><![CDATA[Ben Dodson]]></dc:creator><pubDate>Wed, 15 Feb 2023 14:45:18 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3c1b7700-9355-4bac-81d4-2191a93e71a5_1920x1080.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dear Reader,</p><p>The big news this week is that <em><a href="https://itunes.apple.com/us/app/music-tracker-automatic-tracking/id1062583712?ls=1&amp;mt=8">Music Library Tracker v2.0</a></em> has finally launched on the App Store complete with support for Spatial Audio scanning and notifications. I&#8217;d like to thank all of you that took part in the TestFlight beta and provided feedback, especially Steve Martin who was a huge help in fixing a number of issues. The TestFlight version will remain working until Monday 20th February at which point you&#8217;ll need to migrate to the <a href="https://itunes.apple.com/us/app/music-tracker-automatic-tracking/id1062583712?ls=1&amp;mt=8">App Store version</a> (there are a number of changes since the last beta build which I&#8217;ll dive into shortly).</p><p>Unfortunately the launch was marred slightly by a rare issue that was affecting a handful of users. The scanning engine would pause half way through the Spatial Audio matching process meaning the app would need to be force quit (which did not necessarily stop the issue from happening again on the next launch); not a great look, especially as the reports came in just as I&#8217;d finished sending out emails to potential reviewers. It turns out it was a threading issue; the code that runs when you have songs missing from my database was not returning back in the same thread so there was a random chance the app would just stop execution at that point. The fix was quick and luckily Apple were able to approve v2.0.1 within a few hours so hopefully no lasting damage done.</p><p>Speaking of reviews, it would be a huge help to me if you were able to leave a review on the App Store or recommend the app to anybody you know who may find it of interest. It&#8217;s currently on a 25% discount for the next week (so $2.99 or thereabouts) and is a free update if you&#8217;ve already purchased it, even if that was 7 years ago when the app first launched. My thanks in advance!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://itunes.apple.com/us/app/music-tracker-automatic-tracking/id1062583712?action=write-review&quot;,&quot;text&quot;:&quot;Leave a Review on the App Store&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://itunes.apple.com/us/app/music-tracker-automatic-tracking/id1062583712?action=write-review"><span>Leave a Review on the App Store</span></a></p><p>I&#8217;m going to take a short break and skip the next scheduled issue as, completely by chance, I&#8217;m away on holiday. The next issue will be on the 15th March.</p><p><strong>&#8212; Ben</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/6-launch-party/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/6-launch-party/comments"><span>Leave a comment</span></a></p><h2>Contents</h2><ul><li><p>Redesigning the Music Library Tracker UI</p></li><li><p>Dolby Audio</p></li><li><p>Station Artwork</p></li><li><p>Darkest Dungeon Prototype</p></li><li><p>Recommended Links</p></li><li><p>Roadmap</p></li></ul><h2>Redesigning the Music Library Tracker UI</h2><p>As I mentioned <a href="https://www.thedododeveloper.com/p/5-spatial-scanning">last issue</a>, I wasn&#8217;t particularly happy with the interface of <em>Music Library Tracker</em>. There were three key areas I wanted to improve:</p><ol><li><p>The onboarding process</p></li><li><p>The settings page</p></li><li><p>The experience on iPad</p></li></ol><p>I was able to get all of these done along with some general tidying up around the app to make it look a little better such as changing the old salmon colour for a brighter pink that is present in the app icon.</p><h4>Onboarding</h4><p>For those of you that aren&#8217;t familiar, &#8220;onboarding&#8221; is the process in which you get a user set up on the first launch of an app. This can take the form of tutorials, registration, and so on but for Music Library Tracker the only reason was to gain access to your music library. Here&#8217;s how the old process looked:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vUwZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97ad1014-3e29-4deb-9a38-c35ad764ab46_5980x2936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vUwZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97ad1014-3e29-4deb-9a38-c35ad764ab46_5980x2936.png 424w, https://substackcdn.com/image/fetch/$s_!vUwZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97ad1014-3e29-4deb-9a38-c35ad764ab46_5980x2936.png 848w, https://substackcdn.com/image/fetch/$s_!vUwZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97ad1014-3e29-4deb-9a38-c35ad764ab46_5980x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!vUwZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97ad1014-3e29-4deb-9a38-c35ad764ab46_5980x2936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vUwZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97ad1014-3e29-4deb-9a38-c35ad764ab46_5980x2936.png" width="1456" height="715" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/97ad1014-3e29-4deb-9a38-c35ad764ab46_5980x2936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:715,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2530437,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vUwZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97ad1014-3e29-4deb-9a38-c35ad764ab46_5980x2936.png 424w, https://substackcdn.com/image/fetch/$s_!vUwZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97ad1014-3e29-4deb-9a38-c35ad764ab46_5980x2936.png 848w, https://substackcdn.com/image/fetch/$s_!vUwZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97ad1014-3e29-4deb-9a38-c35ad764ab46_5980x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!vUwZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97ad1014-3e29-4deb-9a38-c35ad764ab46_5980x2936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Yup, that salmon background has really stood the test of time &#129314;</figcaption></figure></div><p>There are a lot of problems here. The design is obviously horrible, there&#8217;s no softening you up for the media library access prompt (which happens when you press &#8220;Get Started&#8221;), and the scanning process didn&#8217;t really need to be here. The only good part was the notifications prompt which correctly details why the permission is needed and presents a &#8220;you really should allow this&#8221; alert if you chose not to enable it.</p><p>With v2.0, I wanted to make the process much smoother as there are a few extra things I wanted to ask permission for:</p><ol><li><p>Media Library access: this is a hard requirement as the app can&#8217;t do anything at all if it doesn&#8217;t have permission to access your library<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>.</p></li><li><p>Spatial Audio scanning: I made this an opt-in feature as it requires sending data to my server.</p></li><li><p>Spatial Audio playlist: if they opted-in to the Spatial Audio feature then do they want the automatic Apple Music playlist?</p></li><li><p>Notifications: there are two types of notification now (although the Spatial Audio one isn&#8217;t shown as an option if Spatial Audio wasn&#8217;t turned on) but I also always want to prompt for notification access so I can send silent push notifications in order to do background scanning of the library.</p></li></ol><p>Initially I was going to do something similar to the previous design; a straightforward left-to-right navigation with each button prompt taking you to the next page. I&#8217;d likely stick this in a single view with a paging indicator to show how far through the process you were but then that would cause problems as you can skip step 3.</p><p>Instead, I decided I wanted to try an interface where text would rise and fade from the bottom of the screen with the various options. It would work somewhat like an automated chat as the response could be dictated by the choices you make so the user doesn&#8217;t need to see they&#8217;ve missed something. I&#8217;m very pleased with the result:</p><div id="youtube2-fWswPP2aTuc" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;fWswPP2aTuc&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/fWswPP2aTuc?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>The process is far smoother and the design looks much, much better. Each section has text that clearly outlines why the permission is needed and then gives the user the option to accept or not. Once the decision is made, the button turns grey (with an optional &#9989; if accepted), the text above fades out, and the next section fades up into view.</p><p>It&#8217;s not shown in the video, but if a user turns down the media library access then a prompt is shown that takes them to the Settings app to turn this on; they can&#8217;t proceed into the app unless that is enabled. Similarly, if the user turns off the toggle for both types of notifications, an extra prompt is shown pointing out that permissions will still be asked for in order to serve silent notifications.</p><p>One element I particularly like about this flow is that I&#8217;m able to drop a user in part way. For example, somebody upgrading from the previous version of the app does not need to enable library access as it is likely already enabled. In that instance, the first thing they are shown is the second section regarding Spatial Audio. This will also work well in the future should I add any additional prompts.</p><p>The entire onboarding prompt was built in SwiftUI as I wanted to make a quick prototype to test whether or not such an interface would actually work the way I envisioned. As I&#8217;ve said a few times before, SwiftUI is perfect for fast experimentation. In particular, the animation of the splash screen icon from the centre of the screen to the title bar is something that would have been very time consuming in UIKit but was an absolute doddle with <em><a href="https://www.hackingwithswift.com/quick-start/swiftui/how-to-synchronize-animations-from-one-view-to-another-with-matchedgeometryeffect">matchedGeometryEffect()</a></em>.</p><h4>Settings</h4><p>I didn&#8217;t have any particular issues with the previous settings page in terms of the design, but I wasn&#8217;t happy with the layout of the new Spatial Audio section:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tjsK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3195d559-10ff-427d-a85c-6a04ef7926e8_1450x2936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tjsK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3195d559-10ff-427d-a85c-6a04ef7926e8_1450x2936.png 424w, https://substackcdn.com/image/fetch/$s_!tjsK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3195d559-10ff-427d-a85c-6a04ef7926e8_1450x2936.png 848w, https://substackcdn.com/image/fetch/$s_!tjsK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3195d559-10ff-427d-a85c-6a04ef7926e8_1450x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!tjsK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3195d559-10ff-427d-a85c-6a04ef7926e8_1450x2936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tjsK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3195d559-10ff-427d-a85c-6a04ef7926e8_1450x2936.png" width="294" height="595.2993103448276" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3195d559-10ff-427d-a85c-6a04ef7926e8_1450x2936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2936,&quot;width&quot;:1450,&quot;resizeWidth&quot;:294,&quot;bytes&quot;:572266,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!tjsK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3195d559-10ff-427d-a85c-6a04ef7926e8_1450x2936.png 424w, https://substackcdn.com/image/fetch/$s_!tjsK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3195d559-10ff-427d-a85c-6a04ef7926e8_1450x2936.png 848w, https://substackcdn.com/image/fetch/$s_!tjsK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3195d559-10ff-427d-a85c-6a04ef7926e8_1450x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!tjsK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3195d559-10ff-427d-a85c-6a04ef7926e8_1450x2936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The issue is that the &#8220;Monitor changes to Spatial Audio&#8221; toggle will hide the playlist section entirely if turned off as that setting would no longer be relevant. However they don&#8217;t look linked together as there is a gap between the two sections (necessary as the playlist piece is distinct with it&#8217;s own footer text).</p><p>This was further compounded by the addition of a new feature, the ability to get a notification when tracks are upgraded to Spatial Audio. This section would similarly need to be hidden if the whole Spatial Audio piece was disabled and I&#8217;d also need to rework how to display the current notification permission status.</p><p>From a coding point of view, the settings page used a static table in a <code>UITableViewController</code> which is why hiding sections became quite difficult. I can&#8217;t just hide a section in the middle of the screen so have to instead give the cells a 0.1pt height and hide the content which is a very hacky solution. This also means that animations didn&#8217;t work so changes are fairly jarring. I did not want to make this worse with the extra toggles that could show and hide content so my options were either a) rebuild it with dynamic cells or b) rewrite it in SwiftUI. I chose the latter!</p><p>The new version looks similar but a lot cleaner:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wdJL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa36e3b0-0a8c-4d37-9ea9-a08c267ea80b_2960x2936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wdJL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa36e3b0-0a8c-4d37-9ea9-a08c267ea80b_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!wdJL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa36e3b0-0a8c-4d37-9ea9-a08c267ea80b_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!wdJL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa36e3b0-0a8c-4d37-9ea9-a08c267ea80b_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!wdJL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa36e3b0-0a8c-4d37-9ea9-a08c267ea80b_2960x2936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wdJL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa36e3b0-0a8c-4d37-9ea9-a08c267ea80b_2960x2936.png" width="1456" height="1444" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aa36e3b0-0a8c-4d37-9ea9-a08c267ea80b_2960x2936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1444,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1004371,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wdJL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa36e3b0-0a8c-4d37-9ea9-a08c267ea80b_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!wdJL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa36e3b0-0a8c-4d37-9ea9-a08c267ea80b_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!wdJL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa36e3b0-0a8c-4d37-9ea9-a08c267ea80b_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!wdJL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa36e3b0-0a8c-4d37-9ea9-a08c267ea80b_2960x2936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Each section is now distinct with both a title and footer. They also all make use of <a href="https://developer.apple.com/sf-symbols/">SF Symbols</a> to make the settings page look a little bit more modern. Thanks to SwiftUI, the state of the page is driven by the variables so it&#8217;s trivial to show or hide a section with a simple boolean wrapped in an animation block.</p><p>The previous version would show the status of the system notification permissions with a simple &#8220;Enabled&#8221; or &#8220;Disabled&#8221; label at the top of the page. If permissions had been denied, tapping on this label would show an alert that could take you to the Settings app. I didn&#8217;t want to take up extra real estate to show you notifications <em>were</em> enabled but I did still want to show something if the system permissions weren&#8217;t granted:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!b_pV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67018b6-f1e7-4201-9be0-0d0f06a5943d_600x337.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!b_pV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67018b6-f1e7-4201-9be0-0d0f06a5943d_600x337.gif 424w, https://substackcdn.com/image/fetch/$s_!b_pV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67018b6-f1e7-4201-9be0-0d0f06a5943d_600x337.gif 848w, https://substackcdn.com/image/fetch/$s_!b_pV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67018b6-f1e7-4201-9be0-0d0f06a5943d_600x337.gif 1272w, https://substackcdn.com/image/fetch/$s_!b_pV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67018b6-f1e7-4201-9be0-0d0f06a5943d_600x337.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!b_pV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67018b6-f1e7-4201-9be0-0d0f06a5943d_600x337.gif" width="600" height="337" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a67018b6-f1e7-4201-9be0-0d0f06a5943d_600x337.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:337,&quot;width&quot;:600,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1560243,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!b_pV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67018b6-f1e7-4201-9be0-0d0f06a5943d_600x337.gif 424w, https://substackcdn.com/image/fetch/$s_!b_pV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67018b6-f1e7-4201-9be0-0d0f06a5943d_600x337.gif 848w, https://substackcdn.com/image/fetch/$s_!b_pV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67018b6-f1e7-4201-9be0-0d0f06a5943d_600x337.gif 1272w, https://substackcdn.com/image/fetch/$s_!b_pV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67018b6-f1e7-4201-9be0-0d0f06a5943d_600x337.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If a notification type is enabled but system permissions have been denied,  I tint the field red and add an exclamation icon. When tapped, you get the same prompt that can take you to the Settings app to grant permission<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>. This works a lot better and, again, was a breeze to put together with SwiftUI along with seamless animating of colour and icon with barely any code.</p><p>Updating the settings page isn&#8217;t a big flashy feature but it should make the app far more usable for those who need it whilst also making it far more maintainable for me in future. It also just looks a lot more professional than what came before.</p><p></p><h4>iPad Improvements</h4><p>The final area I wanted to improve was the experience on iPad. Whilst the app has always been a Universal binary rather than a blown up iPhone app, it has still always looked a bit silly on the larger iPads as there just isn&#8217;t that much content to show.</p><p>I&#8217;ve improved this by ensuring that all views conform to the <a href="https://developer.apple.com/documentation/uikit/uiview/1622644-readablecontentguide">readable content guide</a> which essentially adds some white space to the sides depending on how large the device is.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YLK6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec7c90-f464-498c-aeb9-c874416496ac_5847x2630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YLK6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec7c90-f464-498c-aeb9-c874416496ac_5847x2630.png 424w, https://substackcdn.com/image/fetch/$s_!YLK6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec7c90-f464-498c-aeb9-c874416496ac_5847x2630.png 848w, https://substackcdn.com/image/fetch/$s_!YLK6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec7c90-f464-498c-aeb9-c874416496ac_5847x2630.png 1272w, https://substackcdn.com/image/fetch/$s_!YLK6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec7c90-f464-498c-aeb9-c874416496ac_5847x2630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YLK6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec7c90-f464-498c-aeb9-c874416496ac_5847x2630.png" width="1456" height="655" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a9ec7c90-f464-498c-aeb9-c874416496ac_5847x2630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:655,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1755711,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YLK6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec7c90-f464-498c-aeb9-c874416496ac_5847x2630.png 424w, https://substackcdn.com/image/fetch/$s_!YLK6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec7c90-f464-498c-aeb9-c874416496ac_5847x2630.png 848w, https://substackcdn.com/image/fetch/$s_!YLK6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec7c90-f464-498c-aeb9-c874416496ac_5847x2630.png 1272w, https://substackcdn.com/image/fetch/$s_!YLK6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec7c90-f464-498c-aeb9-c874416496ac_5847x2630.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It can still feel a little blown up but I think it&#8217;ll do as a stopgap solution for now. There has been support for split views in the app since day one and I suspect that is how most people on iPad will use it.</p><p>I&#8217;ve also added some keyboard shortcuts to the iPad version and support for trackpads with the interaction pointer:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5k_4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ac220a4-6f7b-439c-822a-06bd93cd4c6f_2630x1908.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5k_4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ac220a4-6f7b-439c-822a-06bd93cd4c6f_2630x1908.png 424w, https://substackcdn.com/image/fetch/$s_!5k_4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ac220a4-6f7b-439c-822a-06bd93cd4c6f_2630x1908.png 848w, https://substackcdn.com/image/fetch/$s_!5k_4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ac220a4-6f7b-439c-822a-06bd93cd4c6f_2630x1908.png 1272w, https://substackcdn.com/image/fetch/$s_!5k_4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ac220a4-6f7b-439c-822a-06bd93cd4c6f_2630x1908.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5k_4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ac220a4-6f7b-439c-822a-06bd93cd4c6f_2630x1908.png" width="1456" height="1056" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1ac220a4-6f7b-439c-822a-06bd93cd4c6f_2630x1908.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1056,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:607160,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5k_4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ac220a4-6f7b-439c-822a-06bd93cd4c6f_2630x1908.png 424w, https://substackcdn.com/image/fetch/$s_!5k_4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ac220a4-6f7b-439c-822a-06bd93cd4c6f_2630x1908.png 848w, https://substackcdn.com/image/fetch/$s_!5k_4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ac220a4-6f7b-439c-822a-06bd93cd4c6f_2630x1908.png 1272w, https://substackcdn.com/image/fetch/$s_!5k_4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ac220a4-6f7b-439c-822a-06bd93cd4c6f_2630x1908.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Buttons in the app will now capture the interaction pointer; on the activity page, a highlight is added as you move around the interface.</figcaption></figure></div><div><hr></div><p>With these three improvements I think the app is looking in much better shape whilst also being a lot more maintainable from a code standpoint thanks to a gradual transition to using SwiftUI.</p><p>Unless there are any major issues I&#8217;m planning on taking a break from this project for a while as I feel a little burnt out after working on it for so many months. That said, I am very happy to see the Spatial Audio feature out in the wild and hope that you&#8217;ll find it to be a useful feature.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://itunes.apple.com/us/app/music-tracker-automatic-tracking/id1062583712?ls=1&amp;mt=8&quot;,&quot;text&quot;:&quot;Download Music Library Tracker v2.0.1&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://itunes.apple.com/us/app/music-tracker-automatic-tracking/id1062583712?ls=1&amp;mt=8"><span>Download Music Library Tracker v2.0.1</span></a></p><p></p><h2>Dolby Audio</h2><p><a href="https://www.thedododeveloper.com/p/5-spatial-scanning">Last issue</a> I mentioned that I was only marking Dolby Atmos tracks as Spatial Audio yet Dolby Audio tracks should also qualify for that moniker as they support Fixed Spatial Audio:</p><blockquote><p>So, at the moment <em>Music Library Tracker</em> and the <em>Spatial Audio Finder</em> only treat Dolby Atmos tracks as being Spatial Audio. I do have a record of which tracks are in Dolby Audio so it should be relatively easy to add that as a feature but it will likely be for a later version!</p></blockquote><p>I have now made a change to the server code so Dolby Audio tracks will now show up as &#8220;Spatial Audio&#8221; within <em><a href="https://itunes.apple.com/us/app/music-tracker-automatic-tracking/id1062583712?ls=1&amp;mt=8">Music Library Tracker</a></em>, the<em> <a href="https://bendodson.com/projects/spatial-audio-finder/">Spatial Audio Finder</a></em>, and <em><a href="https://twitter.com/NewSpatialAudio">@NewSpatialAudio</a></em> on Twitter.</p><p>It will likely be a few days until the existing tracks in the database are updated as new tracks take priority for my scanning engine; as of right now, there are just under 2 million tracks that have been added thanks to the new app update &#128517;</p><p></p><h2>Station Artwork</h2><p>I received a request <a href="https://mastodon.social/@jasonbaroni/109838189880400225">on Mastodon</a> last week regarding Radio Show artwork on Apple Music:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://mastodon.social/@jasonbaroni/109838189880400225" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0Hm9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8666b0-db3c-4ee1-8468-0a53d66abddd_1154x1784.png 424w, https://substackcdn.com/image/fetch/$s_!0Hm9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8666b0-db3c-4ee1-8468-0a53d66abddd_1154x1784.png 848w, https://substackcdn.com/image/fetch/$s_!0Hm9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8666b0-db3c-4ee1-8468-0a53d66abddd_1154x1784.png 1272w, https://substackcdn.com/image/fetch/$s_!0Hm9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8666b0-db3c-4ee1-8468-0a53d66abddd_1154x1784.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0Hm9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8666b0-db3c-4ee1-8468-0a53d66abddd_1154x1784.png" width="496" height="766.7798960138648" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ad8666b0-db3c-4ee1-8468-0a53d66abddd_1154x1784.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1784,&quot;width&quot;:1154,&quot;resizeWidth&quot;:496,&quot;bytes&quot;:2278785,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://mastodon.social/@jasonbaroni/109838189880400225&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0Hm9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8666b0-db3c-4ee1-8468-0a53d66abddd_1154x1784.png 424w, https://substackcdn.com/image/fetch/$s_!0Hm9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8666b0-db3c-4ee1-8468-0a53d66abddd_1154x1784.png 848w, https://substackcdn.com/image/fetch/$s_!0Hm9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8666b0-db3c-4ee1-8468-0a53d66abddd_1154x1784.png 1272w, https://substackcdn.com/image/fetch/$s_!0Hm9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8666b0-db3c-4ee1-8468-0a53d66abddd_1154x1784.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This type of artwork was supported in my <a href="https://bendodson.com/projects/apple-music-artwork-finder/">Apple Music Artwork Finder</a> but it would return an image in a 4:1 aspect ratio with a <a href="https://music.apple.com/gb/curator/rocket-hour/993269779">solid</a> colour background:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KxF9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06658ab7-504f-45b7-80ea-55da6808346d_4320x1080.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KxF9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06658ab7-504f-45b7-80ea-55da6808346d_4320x1080.jpeg 424w, https://substackcdn.com/image/fetch/$s_!KxF9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06658ab7-504f-45b7-80ea-55da6808346d_4320x1080.jpeg 848w, https://substackcdn.com/image/fetch/$s_!KxF9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06658ab7-504f-45b7-80ea-55da6808346d_4320x1080.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!KxF9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06658ab7-504f-45b7-80ea-55da6808346d_4320x1080.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KxF9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06658ab7-504f-45b7-80ea-55da6808346d_4320x1080.jpeg" width="1456" height="364" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/06658ab7-504f-45b7-80ea-55da6808346d_4320x1080.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:364,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:339410,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KxF9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06658ab7-504f-45b7-80ea-55da6808346d_4320x1080.jpeg 424w, https://substackcdn.com/image/fetch/$s_!KxF9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06658ab7-504f-45b7-80ea-55da6808346d_4320x1080.jpeg 848w, https://substackcdn.com/image/fetch/$s_!KxF9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06658ab7-504f-45b7-80ea-55da6808346d_4320x1080.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!KxF9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06658ab7-504f-45b7-80ea-55da6808346d_4320x1080.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p><em>Sidenote: Before I carry on with this there is something that really bugs me about Apple Music radio shows. The show itself (i.e. &#8220;<a href="https://music.apple.com/gb/curator/rocket-hour/993269779">Elton John&#8217;s Rocket Hour</a>&#8221;) is actually known as an &#8220;Apple Curator&#8221; internally whereas an <a href="https://music.apple.com/gb/station/taron-egerton/ra.1463202405">episode of a show</a> is known as a &#8220;Station&#8221;.</em></p><p>For some reason the Apple Music API is returning these images in this 4:1 banner format even though I don&#8217;t believe they are used this way anywhere within the app (whereas the show itself does have a banner image which <em>is</em> shown on it&#8217;s page). I&#8217;ve fixed this within the artwork finder so it will now return a high resolution square image (usually 4320x4320px) but also return the 4:1 banner version just in case it does show something different.</p><p></p><h2>Darkest Dungeon Prototype</h2><p>I&#8217;ve been playing a lot of the <a href="https://mythicgames.net/game/darkest-dungeon/">Darkest Dungeon boardgame</a> recently but have been running into issues with table space. As is very common with these huge co-operative games, it can scale from 1-4 players by changing how many characters you control. There are always 4 characters so if you&#8217;re playing solo you need to make space for each of their boards whereas if you had a full complement of players you&#8217;d have a single character each in front of you.</p><p>Here&#8217;s an example from the rulebook showing how a standard setup should look:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rq1v!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0ed58ae-2bd0-488e-8609-011d7236e8bb_1756x2218.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rq1v!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0ed58ae-2bd0-488e-8609-011d7236e8bb_1756x2218.png 424w, https://substackcdn.com/image/fetch/$s_!rq1v!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0ed58ae-2bd0-488e-8609-011d7236e8bb_1756x2218.png 848w, https://substackcdn.com/image/fetch/$s_!rq1v!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0ed58ae-2bd0-488e-8609-011d7236e8bb_1756x2218.png 1272w, https://substackcdn.com/image/fetch/$s_!rq1v!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0ed58ae-2bd0-488e-8609-011d7236e8bb_1756x2218.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rq1v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0ed58ae-2bd0-488e-8609-011d7236e8bb_1756x2218.png" width="1456" height="1839" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f0ed58ae-2bd0-488e-8609-011d7236e8bb_1756x2218.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1839,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:5393426,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rq1v!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0ed58ae-2bd0-488e-8609-011d7236e8bb_1756x2218.png 424w, https://substackcdn.com/image/fetch/$s_!rq1v!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0ed58ae-2bd0-488e-8609-011d7236e8bb_1756x2218.png 848w, https://substackcdn.com/image/fetch/$s_!rq1v!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0ed58ae-2bd0-488e-8609-011d7236e8bb_1756x2218.png 1272w, https://substackcdn.com/image/fetch/$s_!rq1v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0ed58ae-2bd0-488e-8609-011d7236e8bb_1756x2218.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">That&#8217;s a lot of pieces for a solo player &#129315;</figcaption></figure></div><p>As you can see, there is rather a lot to manage there! I won&#8217;t go into the mechanics of the game in detail as it&#8217;s not relevant but suffice to say most of the action takes place in the top part of this image with combat between characters and enemies as well as navigation through a dungeon. There&#8217;s also an area called the Hamlet which you visit and upgrade between quests that is not shown here so you actually need even more space.</p><p>To make it easier for me to play on my own I&#8217;m proposing to digitise the entirety of the bottom half of that image. I&#8217;m imagining a companion app where you would put which characters you are using and choose their relevant skills. I could then add status effects and tokens to the app and have it track their health and all those other cards such as diseases, curios, virtues, etc. That would significantly reduce the amount of stuff that needs to be on the table and make it a much better experience for solo play.</p><p>Whilst I often start prototyping an idea directly in code, sometimes I like to draw a basic mockup on my iPad using an Apple Pencil. These mockups are crude at best:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_rdw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05afd688-a766-47a0-bbc7-cd7fba2da7fe_5320x1908.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_rdw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05afd688-a766-47a0-bbc7-cd7fba2da7fe_5320x1908.png 424w, https://substackcdn.com/image/fetch/$s_!_rdw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05afd688-a766-47a0-bbc7-cd7fba2da7fe_5320x1908.png 848w, https://substackcdn.com/image/fetch/$s_!_rdw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05afd688-a766-47a0-bbc7-cd7fba2da7fe_5320x1908.png 1272w, https://substackcdn.com/image/fetch/$s_!_rdw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05afd688-a766-47a0-bbc7-cd7fba2da7fe_5320x1908.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_rdw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05afd688-a766-47a0-bbc7-cd7fba2da7fe_5320x1908.png" width="1456" height="522" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/05afd688-a766-47a0-bbc7-cd7fba2da7fe_5320x1908.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:522,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3143969,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_rdw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05afd688-a766-47a0-bbc7-cd7fba2da7fe_5320x1908.png 424w, https://substackcdn.com/image/fetch/$s_!_rdw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05afd688-a766-47a0-bbc7-cd7fba2da7fe_5320x1908.png 848w, https://substackcdn.com/image/fetch/$s_!_rdw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05afd688-a766-47a0-bbc7-cd7fba2da7fe_5320x1908.png 1272w, https://substackcdn.com/image/fetch/$s_!_rdw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05afd688-a766-47a0-bbc7-cd7fba2da7fe_5320x1908.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">I&#8217;ll be impressed if you can read my writing</figcaption></figure></div><p>You&#8217;re likely not going to be able to make heads nor tails of what that means so why am I sharing it? The point is that <em>I know</em> what it means and it&#8217;s all I need to have before diving into the actual coding. Too many people get hung up with having the perfect prototyping systems; I&#8217;ve seen many people build pixel perfect wireframes that take days to put together and offer no additional benefit beyond a quick sketch. A prototype should be something to quickly iron out &#8220;will this work&#8221;, it should not be a cast in stone blueprint for an app.</p><p>I don&#8217;t have anything else to say about this app for the time being but it&#8217;s going to be a fun diversion for me over the next couple of weeks, a palate cleanser before I dive back into some proper apps for release later in the year.</p><p></p><h2>Recommended Links</h2><h4>Video Games</h4><ul><li><p><em><a href="https://www.nintendo.co.uk/Nintendo-Switch-Online/Nintendo-Switch-Online-Expansion-Pack/Nintendo-Switch-Online-Expansion-Pack-2060571.html">The Minish Cap</a></em> - There was a Nintendo Direct last week in which it was announced that GameBoy and GameBoy Advance games would now be available via the Switch Online Expansion Pack. This Zelda game caught my eye as it&#8217;s one of the few in the series I haven&#8217;t played. It looks absolutely gorgeous on the Switch OLED and I&#8217;ve really enjoyed the gameplay so far after a few hours. Can&#8217;t wait to get stuck into more of it over the next week.</p></li><li><p><em><a href="https://returntomonkeyisland.com">Return to Monkey Island</a></em> - Whilst I&#8217;m a big fan of point and click adventures, I&#8217;ve never really been that into Monkey Island (I think <em>Day of the Tentacle</em> and <em>Thimbleweed Park</em> are much better). That said, I was excited when this appeared at launch on Game Pass and managed to burn through it in around 6 hours. I really like the new art style which has a certain <em>Ren &amp; Stimpy</em> vibe to it (especially the scurvydog &#129314;). The puzzles are a little easy to begin with but really hit their stride in Act 4. Unfortunately the ending is a bit of a let down both in terms of story and gameplay but overall it&#8217;s something I&#8217;d recommend.</p></li></ul><h4>Board Games</h4><ul><li><p><em><a href="https://www.looneylabs.com/games/pirate-fluxx">Pirate Fluxx</a></em> - I&#8217;ve always had a bit of a soft spot for <em>Fluxx</em>, the ever changing card game. It went a bit off the rails with it&#8217;s later expansions but the era of <em>Pirate Fluxx</em> and <em>Monty Python and the Holy Grail Fluxx</em> was where it hit it&#8217;s peak in terms of balance between complexity of rules and simplicity of play. I played a 30 minute game of this with some friends and was instantly transported back 12 years or so. A wonderful game and the perfect way to cap off an evening if a larger board game has wrapped up earlier than expected.</p></li></ul><h4>Music</h4><ul><li><p><em><a href="https://music.apple.com/gb/album/this-is-why/1646945378">This Is Why by Paramore</a></em> - The 6th album by Paramore dropped a few days ago and has been stuck on repeat on my AirPods. I&#8217;ve enjoyed every track but will give a special shout out to <em>C&#8217;est Comme &#199;a</em>, <em>Running Out Of Time</em>, and the titular <em>This Is Why</em> (the opening of which sounds a lot like one of my favourite bands, <a href="https://thebombayroyale.com">The Bombay Royale</a>). The full album is available in Spatial Audio, but hopefully you already have an app that will tell you that &#128521;</p></li></ul><h4>TV Shows</h4><ul><li><p><em><a href="https://www.netflix.com/title/80992058">Kaleidoscope</a></em> - I&#8217;m an absolute sucker for anything to do with heists so was instantly intrigued about this Netflix series starring Giancarlo Esposito. The twist is that the 7 of the 9 episodes<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> can be watched in any order you like; Netflix even randomise them for you! It's an astonising accomplishment as key plot points will unfold differently depending on which order the setup and aftermath of the heist are viewed. The heist itself was serviceable enough but the unique way to watch it is something that will stick with me for a long time.</p></li></ul><p></p><h2>Roadmap</h2><p>The roadmap is my way of committing to what I&#8217;m going to do before the next issue:</p><h4>2nd February - 15th February</h4><ul><li><p>Release <em>Music Library Tracker v2.0  &#9989;</em></p></li><li><p>Start prototyping an app for the <em>Darkest Dungeon</em> board game  &#9989;</p></li></ul><p>After pushing the launch back by 2 weeks I&#8217;m glad I was able to get it released this time around! Definitely looking forward to working on something else for a little while &#129315;</p><h4>16th February - 15th March (Issue #7)</h4><ul><li><p>Complete the <em>Darkest Dungeon</em> app</p></li><li><p>I started working on an app a few months ago called <em>Board Game Lists</em> that will let you create lists of the games you own and export a PDF with some notes attached. The use case for this is that I was arranging a board game night with some neighbours and wanted a way to show what games I had that would be suitable (i.e. the right number of players, difficulty, etc). I&#8217;d like to get back into that as I&#8217;d got an integration with <a href="https://boardgamegeek.com">BoardGameGeek</a> working so just needed to build out some of the other functionality.</p></li></ul><p>That wraps it up for this issue. I hope you found something of interest and that you&#8217;ll be able to recommend the newsletter to your friends, family, and colleagues. You can always comment on this issue or by emailing me directly via <a href="mailto:ben@bendodson.com">ben@bendodson.com</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/subscribe?"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/6-launch-party/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/6-launch-party/comments"><span>Leave a comment</span></a></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>This is bittersweet for me as having to request permissions is a bit of a pain but I was the person <a href="https://bendodson.com/weblog/2016/01/13/your-music-library-is-a-security-and-privacy-risk-on-ios/">campaigning for it to happen</a> back in 2016. It eventually did as part of iOS 10. Thanks for nothing Past Ben &#129315;</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>As of iOS 16 it is now possible to link directly to the notifications section for your app in Settings with <code>UIApplication.openNotificationSettingsURLString</code> - I use this but fall back to <code>UIApplication.openSettingsURLString</code> for earlier versions.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>There are 8 episodes really; the first one, <em>Black</em>, is just a 50 second thing telling you how it can be watched in any order.</p></div></div>]]></content:encoded></item><item><title><![CDATA[#5: Spatial Scanning]]></title><description><![CDATA[Music Library Tracker v2.0 gets automated scanning for Spatial Audio]]></description><link>https://www.thedododeveloper.com/p/5-spatial-scanning</link><guid isPermaLink="false">https://www.thedododeveloper.com/p/5-spatial-scanning</guid><dc:creator><![CDATA[Ben Dodson]]></dc:creator><pubDate>Wed, 01 Feb 2023 14:46:01 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ae724363-4e46-4065-bf71-c1079885bb87_1280x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dear Reader,</p><p>I was reading <a href="https://www.thenewfatherhood.org/">The New Fatherhood</a> recently and came across this brilliant video<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>:</p><div id="vimeo-85040589" class="vimeo-wrap" data-attrs="{&quot;videoId&quot;:&quot;85040589&quot;,&quot;videoKey&quot;:&quot;&quot;,&quot;belowTheFold&quot;:true}" data-component-name="VimeoToDOM"><div class="vimeo-inner"><iframe src="https://player.vimeo.com/video/85040589?autoplay=0" frameborder="0" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" loading="lazy"></iframe></div></div><p>The whole thing is definitely worth 2 minutes of your time but I was particularly taken by this quote:</p><div class="pullquote"><p>&#8220;Everybody I know who does interesting, creative work, they went through years where they had really good taste and they could tell that what they were making wasn't as good as they wanted it to be. They knew it fell short. Everybody goes through that.</p><p>The most important thing you can do is do a lot of work. Do a huge volume of work. Put yourself on a deadline so that every week or every month you know you're going to finish one story. It is only by going through a volume of work that you're going to catch up and close that gap, and the work you're making will be as good as your ambitions.&#8221;</p></div><p>I&#8217;ve always been a big proponent of the &#8220;<a href="https://quoteinvestigator.com/2018/10/13/ship/">real artists ship</a>&#8221; mentality but sometimes you have to take a step back and say &#8220;this isn&#8217;t ready yet&#8221;. So it is with Music Library Tracker v2.0 which I was hoping to have launched yesterday. Instead it&#8217;ll be launching in early February.</p><p>Today&#8217;s issue will be shorter than usual<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> as I&#8217;ve been in crunch mode to get the app update finished but I hope you&#8217;ll find something of interest. I look forward to hearing any feedback you may have.</p><p><strong>&#8212; Ben</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/5-spatial-scanning/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/5-spatial-scanning/comments"><span>Leave a comment</span></a></p><h2>Contents</h2><ul><li><p>An update on Music Library Tracker v2.0</p></li><li><p>Moving to Mastodon</p></li><li><p>Questions</p></li><li><p>Recommended Links</p></li><li><p>Roadmap</p></li></ul><h2>An update on Music Library Tracker v2.0 </h2><p>As mentioned in the introduction, I&#8217;ve been hard at work on <em>Music Library Tracker v2.0</em> and pushed out a couple of updates on TestFlight at the start of the week. I still have some UI tidying I want to do and one extra feature I want to add but for now I thought I&#8217;d do a deep dive into some of the new features added in the past 2 weeks.</p><h4>Speed Improvements</h4><p>The main draw of the app is that it can detect changes in your music library. This is not done via some fancy API though; it&#8217;s a very blunt comparison check<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>. The app goes through a 4 step process:</p><ol><li><p>Fetching: The media library is retrieved using <a href="https://developer.apple.com/documentation/mediaplayer/mpmedialibrary/1621272-begingeneratinglibrarychangenoti">MPMediaLibrary</a> and stored in a local database</p></li><li><p>Changing: I check each track to see if it was previously stored in the database; if it was, then I check each piece of metadata to see if it changed; if it wasn&#8217;t, then I add it as a new track. </p></li><li><p>Deleting: I then do the reverse of the &#8220;changing&#8221; step by checking if any tracks were present in the database previously but are not in the latest fetch indicating that they have been deleted.</p></li><li><p>Writing: The generated logs for all of the changes are written to the database along with the latest fetch information so when this runs again I can see the fresh changes.</p></li></ol><p>One key part of this process is that it can run in the background. To do this, the app is sent a silent push notification every hour which wakes the app up in the background and starts this process. Apps that are woken like this only have 30 seconds to do their work and have significantly less system resources available to them which means this entire process is roughly 5-10x slower. Some users have huge libraries (i.e. over 100k songs) so this process would quickly stop working in the background if it weren&#8217;t for the batch system I&#8217;ve created which means that data is saved at each step after every 500 iterations and can then be resumed if need be. For example, lets say in the fetching phase I&#8217;m churning through 80k tracks; after I&#8217;ve fetched 500, I save this data locally before moving to the next 500 and so on. After each batch, I can check how much time is left<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a> and then make a decision as to whether I want to try for another batch or abandon early.</p><p>It turns out that with my library of around 8k tracks, the <em>fetching</em> phase was by far the slowest taking up 4.5 seconds of the overall 4.7 seconds required when running in the foreground.</p><p>Previously I would use <code>item.value(forProperty:)</code> to get each piece of metadata but when working on the Spatial Audio features last month I noticed I could just access them directly via properties i.e. using <code>item.title</code> instead of <code>item.value(forProperty: MPMediaItemPropertyTitle)</code>.</p><p>This led to some marginal gains that were within the margins of error so I took another look at the documentation and found there was a function named <code>enumerateValues(forProperties:using:)</code> which Apple describes as:</p><blockquote><p>Anytime the app accesses more than one property, enumerating over a set of property keys is more efficient than fetching each individual property</p></blockquote><p>It looks like I&#8217;d tried this previously as I had some commented out code using this feature. I decided to rewrite it and found it was crashing the app. Hmm. This is likely why it was commented out &#129315;</p><p>It turns out that the feature is broken due to the <a href="https://developer.apple.com/forums/thread/658171">bridge between Objective-C and Swift</a>. Luckily Stephen Heaps had put together <a href="https://gist.github.com/StephenHeaps/40ea93012600f7e9abad9bd9bdc9084b">a fix in Objective-C</a> so all I had to do was import that via a bridging header and use the fixed method.</p><p>The speed improvements were dramatic. What was previously taking 4.5 seconds was now taking 0.6 seconds. An almost 10x speed boost! This is especially important in the background as it now means the entire process can complete in less than a 30-second window meaning the tracking happens much more reliably. For my small library this means I get a full 24 scans per day but for those with huge libraries this should still mean the entire library gets done daily which is the key thing.</p><h4>Background Spatial Audio</h4><p>With the scanning engine dramatically sped up, it gave me the headroom required to think about how I could check for Spatial Audio in the background. Back in <a href="https://www.thedododeveloper.com/p/1-catacombs-spatial-audio-and-animated">Issue #1</a> I&#8217;d said:</p><blockquote><p>As Music Library Tracker is a one time purchase it isn&#8217;t feasible to allow for checking to happen in the background all the time in the same way it does for checking your local media as a) it is likely too slow given the number of network requests and b) all the bandwidth usage literally costs me money so I&#8217;d end up paying more than people paid for the app (especially if they bought it on sale 6 years ago when it first launched). I don&#8217;t really want to do subscription pricing for this sort of thing so I think the only option is to have the check be manual from within the app and perhaps limited to once per day. That way you can go and see what songs have been updated when you think to do it and there isn&#8217;t wasted bandwidth being used in the background by every user. Perhaps in the future I could look to adding a cheap subscription for those users who do want to have automatic notifications but that&#8217;s definitely for much further in the future.</p></blockquote><p>I&#8217;m happy to say that I&#8217;ve been able to get it all working without the need for any subscriptions; it&#8217;s free for every user of the app!</p><p>The first thing to do was to add a new <em>spatialAudio </em>step between <em>fetching</em> and <em>changing</em>. This fetches a batch of 2500 ids from the local database and uploads them to my server for checking, exactly as described in <a href="https://www.thedododeveloper.com/p/2-music-library-tracker-v20">Issue #2</a>. With the response, I then check against the local track stored in the database and update it with the new value so that when the <em>changing</em> step happens it will notice that is now or now not a Spatial Audio track. At the same point in the <em>changing</em> step, if a track is found to now be Spatial Audio a record is created so that it appears in the Spatial Audio tab (and similarly removed if the track is no longer in Spatial Audio).</p><p>This raised a problem though. During the <em>changing</em> step I no longer have access to the MPMediaItem which is retrieved during <em>fetching</em> but I need this in order to add the track to the Apple Music playlist. When this feature was only in the foreground it was trivial for me to just fetch all the tracks again and store them in memory but this won&#8217;t work in the background without doubling the time the process takes. Instead, I took another look at the documentation and found there is a method named <a href="https://developer.apple.com/documentation/mediaplayer/mpmedialibrary/1621278-additem">addItem(withProductID:)</a> which looked as though it would work as I do have the id at this point in the process.</p><p>It does work but it adds another minor headache. Whereas adding an item with an MPMediaItem is practically instant (as it&#8217;s done locally on the device), adding an item via it&#8217;s product ID seems to make a network request to the Apple Music API to add the track. This means it&#8217;s asynchronous and <em>not</em> instant. Thankfully everything seems to work even if I throw 6k ids at it in the space of a few seconds but it can take up to half an hour before those tracks are fully ready in the playlist.</p><p>In the end I decided this was a trade-off worth having as in most instances you are not going to be cognisant of the fact that a track has been added until several hours after the fact as I only tell you about changes that happened yesterday (thus the track may start trying to add at 00.30, be added a few minutes later, and then you&#8217;ll get a notification the following day at around 08.00).</p><p>The final step in adding Spatial Audio to the background scanner was to ensure that any songs that weren&#8217;t in my database were uploaded. To do this, I add the identifiers of missing tracks to the local database and then upload them in batches of 5000 in a new step between <em>spatialAudio</em> and <em>checking</em>. Again, the whole process is done in batches so it can be terminated and resumed at a moments notice.</p><p>Whilst I haven&#8217;t added limits to this feature when running in the foreground, I have set this up so that Spatial Audio is only checked once per day when running in the background. This prevents thousands of requests hitting the server every hour and means you&#8217;ll still get notified when audio in your library is upgraded without you needing to open the app every day.</p><h4>Spatial Audio Privacy</h4><p>One thing I&#8217;m very aware of is that the new Spatial Audio features add an element of privacy invasion that I only feel comfortable doing with express permission. Specifically, the app sends the identifiers of your tracks to a remote server in order to match which ones are upgraded or not. Whilst I do not get any personally identifiable information and do not store the ids longer than it takes to serve the request, it is still important to me that the user opts-in as it is not impossible to fingerprint a user from a hash of their library identifiers.</p><p>To solve that, I have built what is hopefully a very clear screen indicating that the user needs to opt-in to Spatial Audio scanning along with a link to my privacy policy. The user can also opt-out from the settings page at any time:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!djjH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc2b5a1e-9e0f-4ddb-b2c5-7b8adb1da890_4137x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!djjH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc2b5a1e-9e0f-4ddb-b2c5-7b8adb1da890_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!djjH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc2b5a1e-9e0f-4ddb-b2c5-7b8adb1da890_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!djjH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc2b5a1e-9e0f-4ddb-b2c5-7b8adb1da890_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!djjH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc2b5a1e-9e0f-4ddb-b2c5-7b8adb1da890_4137x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!djjH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc2b5a1e-9e0f-4ddb-b2c5-7b8adb1da890_4137x2716.png" width="1456" height="956" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cc2b5a1e-9e0f-4ddb-b2c5-7b8adb1da890_4137x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:956,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1711132,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!djjH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc2b5a1e-9e0f-4ddb-b2c5-7b8adb1da890_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!djjH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc2b5a1e-9e0f-4ddb-b2c5-7b8adb1da890_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!djjH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc2b5a1e-9e0f-4ddb-b2c5-7b8adb1da890_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!djjH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc2b5a1e-9e0f-4ddb-b2c5-7b8adb1da890_4137x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I haven&#8217;t yet added this to the onboarding flow as that is part of the UI I want to redesign before launch. As you can see in the screenshots above, I&#8217;ve also added a prompt for whether the user wants to enable the automatic playlist within Apple Music.</p><h4>Ch-ch-ch-changes</h4><p>As I eluded to above, Spatial Audio is now listed as a metadata change which means it will show up as a change alongside things like title, album, artist, etc. This means it will also tell you when a track has Spatial Audio removed, something I mentioned as a potential problem in <a href="https://www.thedododeveloper.com/p/3-yule-logging">Issue #3</a>. I&#8217;ve also added support for track number and disc number and I&#8217;ve stopped minor diacratic alterations from showing up as changes (so a genre updatin from Electr&#243;nica to Electronica will no longer show as a change).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!61rv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4367c877-3cbb-4db3-9f77-5688af6e64ad_2960x2936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!61rv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4367c877-3cbb-4db3-9f77-5688af6e64ad_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!61rv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4367c877-3cbb-4db3-9f77-5688af6e64ad_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!61rv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4367c877-3cbb-4db3-9f77-5688af6e64ad_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!61rv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4367c877-3cbb-4db3-9f77-5688af6e64ad_2960x2936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!61rv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4367c877-3cbb-4db3-9f77-5688af6e64ad_2960x2936.png" width="480" height="476.04395604395603" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4367c877-3cbb-4db3-9f77-5688af6e64ad_2960x2936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1444,&quot;width&quot;:1456,&quot;resizeWidth&quot;:480,&quot;bytes&quot;:1604757,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!61rv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4367c877-3cbb-4db3-9f77-5688af6e64ad_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!61rv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4367c877-3cbb-4db3-9f77-5688af6e64ad_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!61rv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4367c877-3cbb-4db3-9f77-5688af6e64ad_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!61rv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4367c877-3cbb-4db3-9f77-5688af6e64ad_2960x2936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>Other minor updates</h4><ul><li><p>When you choose to &#8220;Regenerate Apple Music Playlist&#8221; within settings, the app will now open the Apple Music app and take you to the Library tab. It felt like something needed to happen once the loading box had closed! I wanted this to go straight to the playlist but that isn&#8217;t possible unfortunately.</p></li><li><p>You may remember I mentioned that the playlist was being created with the author name as &#8220;MusicLog&#8221;. For some reason I completely missed that there was an <code>authorDisplayName</code> property on <code>MPMediaPlaylistCreationMetadata</code> &#129318;&#127995;&#8205;&#9794;&#65039;. There was also a <code>descriptionText</code> property so now the playlist has a description as well!</p></li><li><p>I fixed an issue with sort filters not working properly within the &#8220;Deleted&#8221; section</p></li><li><p>I moved the settings page out of the tab bar and put it in the top left hand corner. The reason for this is that I needed to remove the Spatial Audio tab from macOS (as mentioned <a href="https://www.thedododeveloper.com/p/3-yule-logging">previously</a>) and so I thought it better just to remove the tab bar entirely in that circumstance.</p></li></ul><div><hr></div><p>10x faster scanning, Spatial Audio in the background, privacy improvements, more metadata tracking, and some other fixes. I think that&#8217;ll do for now!</p><p>The last few things I want to sort before launch are UI improvements across the board (especially on iPad which is in sore need of some attention) and adding a new notification for Spatial Audio. At the moment you get notifications when tracks are changed but I&#8217;d like to add a separate notification that can say something like &#8220;3 tracks have been upgraded to Spatial Audio&#8221; as I suspect there will be some users who don&#8217;t care about other changes to their libraries.</p><p>I hope and expect to get that all finished early next week so it can be submitted. Then I can begin on a campaign of promotion to try and get it in front of as many reviewers as possible for a big launch on the 13th February.</p><p>If you have not yet tried out the update, you can use the button below to download the beta for free.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://testflight.apple.com/join/uKS7gUcm&quot;,&quot;text&quot;:&quot;Join TestFlight beta&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://testflight.apple.com/join/uKS7gUcm"><span>Join TestFlight beta</span></a></p><p><em>* Music Library Tracker v2.0 will run on any iOS device running iOS 13 or greater. If you already have Music Library Tracker installed then this will update that version; you won&#8217;t be able to downgrade. This beta will expire when the app update is publicly released.</em></p><p>I would greatly appreciate any feedback you may have. You can always comment on this post, provide feedback directly through TestFlight, or email me via <a href="mailto:ben@bendodson.com">ben@bendodson.com</a>.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/5-spatial-scanning/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/5-spatial-scanning/comments"><span>Leave a comment</span></a></p><p><em>Also, feel free to publicly share any screenshots, videos, or thoughts on the app update (although I&#8217;d obviously prefer you share any issues you encounter with me first!)</em></p><h2>Moving to Mastodon</h2><p>I&#8217;ve mostly steered clear of any of the discussion around Twitter and it&#8217;s new direction as I mostly assumed things would stick to the status quo. Whilst I&#8217;ve noticed over the last few months that things tend to get less engagement there due to the algorithm (one of the factors in starting this newsletter in fact), it&#8217;s recently been shown that the infrastructure <a href="https://daringfireball.net/linked/2023/01/23/twitter-frum-crumbling">can&#8217;t keep up with new tweets</a>:</p><blockquote><p>It&#8217;s worse than that, though, because if you were delivered a newspaper with random stories scissored out, you&#8217;d know that there were missing stories. You wouldn&#8217;t know what they were, but you&#8217;d see the gaping holes in the paper. With Twitter now, there&#8217;s no indication that you&#8217;re missing tweets&#8201;&#8212;&#8201;let alone a huge number of tweets. And to be clear, Frum is talking about the &#8220;Following&#8221; timeline, not the &#8220;For You&#8221; algorithmic timeline.</p></blockquote><p>This means that I&#8217;m missing things that people I follow are posting and when I tweet I may as well be shouting into a void. They also cut off the 3rd party Twitter client I was using so that along with the recent release of <a href="https://tapbots.com/ivory/">Ivory</a> has led me to set up an account on Mastodon:</p><p><strong><a href="https://mastodon.bendodson.com/@ben">@ben@bendodson.com</a></strong></p><p>I&#8217;m not planning on abandoning Twitter altogether as there are lots of people I follow there who are not on Mastodon and I&#8217;m not going to dump 5k followers even if most of them probably aren&#8217;t seeing my content!</p><p>I&#8217;m not entirely sold on the premise of Mastodon as a federated system as it adds a significant barrier of entry, especially when the most popular server &#8211; mastodon.social &#8211; isn&#8217;t accepting new sign ups. I decided to go all in and set up my own server using <a href="https://masto.host">Masto.host</a> as that way I could use the domain bendodson.com. The process was very straightforward requiring me only to set up a CNAME with my DNS provider for the subdomain mastodon.bendodson.com. I then had to add a <a href="https://masto.host/mastodon-usernames-different-from-the-domain-used-for-installation/">simple text file</a> and contact Masto.host so they could ensure there was a redirect; that way I could use the domain @bendodson.com directly rather than the mastodon subdomain.</p><p>With a full domain in place it may be that in future I&#8217;ll create accounts for each of my apps so they can post updates, etc. I can then boost them on my own profile. That&#8217;s definitely something to think about at a later date!</p><p>The only other thing this might impact is the <a href="https://twitter.com/newspatialaudio">@NewSpatialAudio</a> account which tweets whenever a new Spatial Audio track is released. Obviously this is likely missing a number of tweets and so I&#8217;m tinkering with an idea on how to display this in a better way as well as providing fuller access to my audio database. More on that in the future!</p><h2>Questions</h2><p>I noticed that I&#8217;d got some feedback from the <em>Music Library Tracker</em> beta via TestFlight but there was no email address attached so I had no way of replying:</p><p><em>Q: Not sure what to make of this, but every so often I find a Dolby album or track that isn&#8217;t showing up in the Finder. This one is a full album in Dolby that doesn&#8217;t get picked up. If it helps, I&#8217;m listening in &#127482;&#127480;</em></p><p>A: This is a good question. I was initially going to reply that the album in question was in Dolby Audio whereas it needs to be in Dolby Atmos to be classed as Spatial Audio. </p><p>Dolby Atmos is full object based 3D audio whereas Dolby Audio is channel based. It is not immediately obvious that there is a difference between the two as the logos are incredibly similar:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3D8i!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33eaf7f8-f6d8-494f-931a-47678a227263_2960x2936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3D8i!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33eaf7f8-f6d8-494f-931a-47678a227263_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!3D8i!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33eaf7f8-f6d8-494f-931a-47678a227263_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!3D8i!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33eaf7f8-f6d8-494f-931a-47678a227263_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!3D8i!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33eaf7f8-f6d8-494f-931a-47678a227263_2960x2936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3D8i!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33eaf7f8-f6d8-494f-931a-47678a227263_2960x2936.png" width="1456" height="1444" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/33eaf7f8-f6d8-494f-931a-47678a227263_2960x2936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1444,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:9290101,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!3D8i!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33eaf7f8-f6d8-494f-931a-47678a227263_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!3D8i!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33eaf7f8-f6d8-494f-931a-47678a227263_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!3D8i!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33eaf7f8-f6d8-494f-931a-47678a227263_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!3D8i!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33eaf7f8-f6d8-494f-931a-47678a227263_2960x2936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Dolby Atmos on the left, Dolby Audio on the right. Not easy to distinguish!</figcaption></figure></div><p>This is impossible to distinguish on macOS where both formats use the same Dolby icon<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2bbG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cfd346-b2e3-4493-bd62-6cd4bb449277_1075x210.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2bbG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cfd346-b2e3-4493-bd62-6cd4bb449277_1075x210.png 424w, https://substackcdn.com/image/fetch/$s_!2bbG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cfd346-b2e3-4493-bd62-6cd4bb449277_1075x210.png 848w, https://substackcdn.com/image/fetch/$s_!2bbG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cfd346-b2e3-4493-bd62-6cd4bb449277_1075x210.png 1272w, https://substackcdn.com/image/fetch/$s_!2bbG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cfd346-b2e3-4493-bd62-6cd4bb449277_1075x210.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2bbG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cfd346-b2e3-4493-bd62-6cd4bb449277_1075x210.png" width="1075" height="210" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/22cfd346-b2e3-4493-bd62-6cd4bb449277_1075x210.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:210,&quot;width&quot;:1075,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:55275,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2bbG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cfd346-b2e3-4493-bd62-6cd4bb449277_1075x210.png 424w, https://substackcdn.com/image/fetch/$s_!2bbG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cfd346-b2e3-4493-bd62-6cd4bb449277_1075x210.png 848w, https://substackcdn.com/image/fetch/$s_!2bbG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cfd346-b2e3-4493-bd62-6cd4bb449277_1075x210.png 1272w, https://substackcdn.com/image/fetch/$s_!2bbG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cfd346-b2e3-4493-bd62-6cd4bb449277_1075x210.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>My understanding has always been that &#8220;Spatial Audio&#8221; is a marketing term created by Apple that denotes their own usage of 3D audio formats. Spatial Audio can offer things like head tracking so as you move your head the audio stays in the same place almost as if it were coming from a stage. Whilst all Spatial Audio tracks use Dolby Atmos, they don&#8217;t <em>have to</em> as it&#8217;s just a term around 3D audio so any future format (and there are lots of them) would work if Apple supported it.</p><p>Except, that isn&#8217;t quite true!</p><p>Dolby Atmos <em>is</em> required if you want to do dynamic head tracking but you can use fixed Spatial Audio if a song supports Dolby Audio:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!OKpw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207dc443-5cff-4282-af3d-57514aaee402_2960x2936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!OKpw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207dc443-5cff-4282-af3d-57514aaee402_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!OKpw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207dc443-5cff-4282-af3d-57514aaee402_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!OKpw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207dc443-5cff-4282-af3d-57514aaee402_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!OKpw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207dc443-5cff-4282-af3d-57514aaee402_2960x2936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!OKpw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207dc443-5cff-4282-af3d-57514aaee402_2960x2936.png" width="1456" height="1444" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/207dc443-5cff-4282-af3d-57514aaee402_2960x2936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1444,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:10939138,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!OKpw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207dc443-5cff-4282-af3d-57514aaee402_2960x2936.png 424w, https://substackcdn.com/image/fetch/$s_!OKpw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207dc443-5cff-4282-af3d-57514aaee402_2960x2936.png 848w, https://substackcdn.com/image/fetch/$s_!OKpw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207dc443-5cff-4282-af3d-57514aaee402_2960x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!OKpw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207dc443-5cff-4282-af3d-57514aaee402_2960x2936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Tapping Spatial Audio on the Multichannel Dolby Audio track just disables it; there is no option for head tracking</figcaption></figure></div><p>In both cases, Apple is referring to this as &#8220;Spatial Audio&#8221; despite the fact it <a href="https://www.google.com/search?client=safari&amp;rls=en&amp;q=site%3Aapple.com+spatial+audio&amp;ie=UTF-8&amp;oe=UTF-8">only ever references Dolby Atmos</a> with this term on it&#8217;s website &#129318;&#127995;&#8205;&#9794;&#65039;</p><p>So, at the moment <em>Music Library Tracker</em> and the <em>Spatial Audio Finder</em> only treat Dolby Atmos tracks as being Spatial Audio. I do have a record of which tracks are in Dolby Audio so it should be relatively easy to add that as a feature but it will likely be for a later version!</p><h2>Recommended Links</h2><h4>Video Games</h4><ul><li><p><em><a href="https://bethesda.net/en/article/1NiBHswbYY2uJSdAUK6Gtg/hi-fi-rush-revealed">Hi-Fi Rush</a></em> - This was a surprise release immediately after the <a href="https://news.xbox.com/en-us/2023/01/11/xbox-bethesda-present-developer-direct-livestream-january-25/">Xbox &amp; Bethesda Developer_Direct</a> last week. It&#8217;s a rhythm-based action game that gives me strong <em>Sunset Overdrive</em> vibes in the best possible way. You run around a beautiful cartoon environment hitting enemies in time to a constant beat. It&#8217;s very forgiving though so worth a try even if you don&#8217;t have a good ear! I was sold the minute the game opened with <a href="https://www.youtube.com/watch?v=a_426RiwST8">Lonely Boy by The Black Keys</a>.</p></li><li><p><em><a href="https://apple.co/squiggle">Squiggle Drop</a></em> - An interesting little mobile game which tasks you with doing small doodles that fall down once completed to solve puzzles. Bit tricky to explain now I think about it but it&#8217;s free on Apple Arcade so just give it a try.</p></li><li><p><a href="https://news.xbox.com/en-us/2023/01/26/goldeneye-007-xbox-game-pass-out-now/">Goldeneye 007</a> - Bond is back! The Switch version is basically a straight up emulation of the N64 version with a lot of control problems (unless you have the wireless N64 pad) but it does have online multiplayer. Meanwhile, it&#8217;s also available on Xbox with Game Pass with some slightly better textures, a 16:9 aspect ratio, and no online functionality. It&#8217;s an absolute mess of licensing agreements but it&#8217;s here and it&#8217;s still a lot of fun on Xbox, surprisingly so!</p></li></ul><h4>Board Games</h4><ul><li><p><em><a href="https://mythicgames.net/game/darkest-dungeon/">Darkest Dungeon</a></em> - I briefly mentioned this in <a href="https://www.thedododeveloper.com/p/2-music-library-tracker-v20">Issue #2</a> but I&#8217;ve had a chance to play it now and yes it&#8217;s very good. I particularly like the battle system which really captures the flavour of the video game. I&#8217;m going to play a ton of this and am definitely considering building an app to speed along solo play as there are almost too many cards to fit on my table.</p></li></ul><h4>TV Shows</h4><ul><li><p><em><a href="https://www.bbc.co.uk/iplayer/episodes/p0dk8t20/our-flag-means-death">Our Flag Means Death</a></em> - I started watching this in June last year<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a> but only got through an episode or two before passing it by for other things. It's become a bit more popular in the UK this past month as it's now available on BBC iPlayer. I jumped back in as it was something I could watch whilst sleeving the cards for <em>Darkest Dungeon</em> as it didn&#8217;t require my undivided attention. It's a comedy about pirates and whilst the initial two episodes are fine it definitely picks up by the end with quite a few surprisingly emotional story arcs. Taika Waititi's Blackbeard is incredibly good (would make a good Jack Sparrow replacement actually) but Rhys Darby's gentleman pirate steals the show.</p></li></ul><h4>Videos</h4><ul><li><p><a href="https://www.youtube.com/watch?v=0gRBCAdC7wI">Virgil Fox playing Bach&#8217;s &#8220;Gigue&#8221; Fugue</a> - <em>"And mind you I heard a swishing in the hall and I turned around and where they had been seated upon blankets they kicked their blankets aside, and at the end of this piece there were 400 young doing a divine gigue to a fugue of Bach. Let's do it!"</em></p></li></ul><h2>Roadmap</h2><p>The roadmap is my way of committing to what I&#8217;m going to do over the next 2 weeks.</p><h4>19th January - 1st February</h4><ul><li><p>Release <em>Music Library Tracker v2.0 &#10060;</em></p></li></ul><p>Unfortunately not this time around. I&#8217;m not a fan of feature creep but I think adding the automatic background scanning for Spatial Audio was worth a couple weeks delay. Can you guess what the plan is for next issue?</p><h4>2nd February - 15th February (Issue #6)</h4><ul><li><p>Release <em>Music Library Tracker v2.0</em></p></li><li><p>Start prototyping an app for the <em>Darkest Dungeon </em>board game (I have another big music app but I need a break from the Apple Music API for a while &#129315;)</p></li></ul><p>That wraps it up for this issue. I hope you found something of interest and that you&#8217;ll be able to recommend the newsletter to your friends, family, and colleagues. You can always comment on this issue or by emailing me directly via <a href="mailto:ben@bendodson.com">ben@bendodson.com</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.thedododeveloper.com/subscribe?"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/5-spatial-scanning/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.thedododeveloper.com/p/5-spatial-scanning/comments"><span>Leave a comment</span></a></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>I found the video in the article <a href="https://www.thenewfatherhood.org/p/the-art-of-noticing">The Art of Noticing</a>.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Narrator: It wasn&#8217;t.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>There is a <code>beginGeneratingLibraryChangeNotifications()</code> method on <a href="https://developer.apple.com/documentation/mediaplayer/mpmedialibrary/1621272-begingeneratinglibrarychangenoti">MPMediaLibrary</a> but this doesn&#8217;t detail the changes nor does it always trigger correctly when changes are made to tracks by Apple (it seems only to trigger when a user initiates a change).</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>Thank you <a href="https://developer.apple.com/documentation/uikit/uiapplication/1623029-backgroundtimeremaining">backgroundTimeRemaining</a>.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>I&#8217;m guessing they have to show the Dolby logo for licensing reasons but it&#8217;s bizarre to me that they don&#8217;t show the Spatial Audio icon as well.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>I know this because I <a href="https://twitter.com/bendodson/status/1537396753984536583">took a screenshot of it</a> when initially revealing <a href="https://dodoapps.io/chaise-longue-to-5k/">Chaise Longue to 5K</a> &#129315;</p></div></div>]]></content:encoded></item><item><title><![CDATA[#4: New Year, New Tracking App]]></title><description><![CDATA[Rebuilding a personal game tracking app with SwiftUI and Swift Charts]]></description><link>https://www.thedododeveloper.com/p/4-new-year-new-tracking-app</link><guid isPermaLink="false">https://www.thedododeveloper.com/p/4-new-year-new-tracking-app</guid><dc:creator><![CDATA[Ben Dodson]]></dc:creator><pubDate>Wed, 18 Jan 2023 14:46:03 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9ba64ede-1c6a-45fe-a8ee-dcf65d6b2149_1920x1080.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dear Reader,</p><p>One of the things I love most about coding is that it is relatively straightforward to create tools that are useful to you and your own personal situation. You can also use the development of these tools as a way to improve or speed up your professional software development.</p><p>For example, I have three apps to discuss this week which are likely of no use to anybody but me. However, they all bring me joy when I use them and I&#8217;ve been able to add functionality or employ certain coding practices for the simple reason of learning how to do them. It may be that I add functionality to determine if it&#8217;s something I should avoid in future or so I have a bit more knowledge on a particular technology if a client asks about it. There have been several times in the past where I&#8217;ve been able to reuse code from a personal project in a client project and save myself days of time as I&#8217;d already made an investment for future Ben<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>.</p><p>If you are a software developer, I&#8217;d encourage you to work on as many side projects as you comfortably can. They&#8217;re a great way to build up a portfolio when you&#8217;re first starting out and even for a seasoned developer they can provide fertile ground for testing out new frameworks and new ideas. I have a small list on my website of <a href="https://bendodson.com/sideprojects/">side projects I&#8217;ve worked on</a>; I&#8217;ll try to update that list over the next few weeks!</p><p>I hope you&#8217;ll find something of interest in today&#8217;s issue and I look forward to hearing any feedback you may have.</p><p><strong>&#8212; Ben</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/4-new-year-new-tracking-app/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/4-new-year-new-tracking-app/comments"><span>Leave a comment</span></a></p><h2>Contents</h2><ul><li><p>Game Track (rebuilding an app in SwiftUI and Charts)</p></li><li><p>Back Seat Shuffle (now on GitHub)</p></li><li><p>Bookmark (adding a predictive feature)</p></li><li><p>Artwork Finder updates (there are always some)</p></li><li><p>Music Library Tracker v2.0 (a brief update and a launch date)</p></li><li><p>My published apps in 2022 (I am terrible at publicity &#128514;)</p></li><li><p>Recommended Links</p></li><li><p>Roadmap</p></li></ul><h2>Game Track</h2><p>As I mentioned <a href="https://www.thedododeveloper.com/p/3-yule-logging">last issue</a>, I love tracking what I do. Video games are my largest hobby by time spent so it is probably understandable that I&#8217;ve had an app to do this for me over the past 4 years.</p><p>It started out as a way to update a website I ran with my good friend John. We wrote game reviews and thought it would be nice to have a page that showed what we&#8217;d been playing recently in a chronological list. The entire website was statically generated using Jekyll so we had a database of all the games we had played that was exported as JSON when compiling the site. The app was therefore very simple and consisted of a list of games (from my database), a list of all possible platforms, and a basic input screen to add number of hours played:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!F9_A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F624117cf-03e3-4a63-ae8d-d58cf0b8cf11_4137x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!F9_A!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F624117cf-03e3-4a63-ae8d-d58cf0b8cf11_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!F9_A!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F624117cf-03e3-4a63-ae8d-d58cf0b8cf11_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!F9_A!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F624117cf-03e3-4a63-ae8d-d58cf0b8cf11_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!F9_A!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F624117cf-03e3-4a63-ae8d-d58cf0b8cf11_4137x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!F9_A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F624117cf-03e3-4a63-ae8d-d58cf0b8cf11_4137x2716.png" width="1456" height="956" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/624117cf-03e3-4a63-ae8d-d58cf0b8cf11_4137x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:956,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:950372,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!F9_A!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F624117cf-03e3-4a63-ae8d-d58cf0b8cf11_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!F9_A!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F624117cf-03e3-4a63-ae8d-d58cf0b8cf11_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!F9_A!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F624117cf-03e3-4a63-ae8d-d58cf0b8cf11_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!F9_A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F624117cf-03e3-4a63-ae8d-d58cf0b8cf11_4137x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A basic app for logging hours played against a certain game and platform.</figcaption></figure></div><p>If we played a game on Steam (which was 90-95% of our usage back in 2018), then I automatically imported the game details and time spent into the database by running a daily script on my server which checked against the <a href="https://developer.valvesoftware.com/wiki/Steam_Web_API#GetPlayerSummaries_.28v0001.29">Steam Web API</a>; the total hours of a game would be compared and if there was a difference the excess would be added as a log for the previous day.</p><p>Whilst this basic setup served a purpose, there were a number of issues:</p><ul><li><p>I had to manually add every game to the database and create some artwork for it (which was a low res 460x215 to match Steam&#8217;s <a href="https://cdn.akamai.steamstatic.com/steam/apps/1172620/header.jpg?t=1673489975">store images</a> - it&#8217;s also an awkward aspect ratio). That little + button you see in the first image? That just sends me an iMessage with the name of the game that John wants to add &#128514;</p></li><li><p>The platform was independent to the game so you&#8217;d always be shown a full list of every platform in the database. My main console is an Xbox Series X so I always had to scroll to the bottom past things I&#8217;m very unlikely to play again (looking at you 3DS &#128064;).</p></li><li><p>As I was doing a diff between our current playtime and Steam&#8217;s playtime, I had to create a new platform called &#8220;PC (Not Steam)&#8221; which we then merged with the PC platform on the website if we played something from Game Pass, UPlay, Epic, etc. Not ideal!</p></li><li><p>Logging a game meant going through these screens every time with multiple taps and scrolls.</p></li></ul><p>I resolved the last issue partially by donating an <a href="https://developer.apple.com/documentation/sirikit/ininteraction">INInteraction</a> using <a href="https://developer.apple.com/documentation/sirikit">SiriKit</a> whenever a log was created. This would appear in the system search as a deep link so you could jump directly to the final stage of the process if you&#8217;d logged against a specific game and platform a couple of times. </p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Rd-M!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbad71550-526d-41f3-ac6f-24d7dd868908_1290x326.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Rd-M!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbad71550-526d-41f3-ac6f-24d7dd868908_1290x326.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Rd-M!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbad71550-526d-41f3-ac6f-24d7dd868908_1290x326.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Rd-M!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbad71550-526d-41f3-ac6f-24d7dd868908_1290x326.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Rd-M!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbad71550-526d-41f3-ac6f-24d7dd868908_1290x326.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Rd-M!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbad71550-526d-41f3-ac6f-24d7dd868908_1290x326.jpeg" width="392" height="99.06356589147286" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bad71550-526d-41f3-ac6f-24d7dd868908_1290x326.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:326,&quot;width&quot;:1290,&quot;resizeWidth&quot;:392,&quot;bytes&quot;:284074,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Rd-M!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbad71550-526d-41f3-ac6f-24d7dd868908_1290x326.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Rd-M!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbad71550-526d-41f3-ac6f-24d7dd868908_1290x326.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Rd-M!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbad71550-526d-41f3-ac6f-24d7dd868908_1290x326.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Rd-M!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbad71550-526d-41f3-ac6f-24d7dd868908_1290x326.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Even this was a bit hit and miss though as typically I&#8217;d have to search in Spotlight and it would appear under &#8220;Show More Results&#8221; even if I had interacted with the app every single day<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>.</p><p>Due to the above issues, we weren&#8217;t always logging our games, especially if we only played them for a short while as it wasn&#8217;t worth the effort of adding a new title and even adding titles we played regularly was arduous.</p><p>A new app was needed! Introducing, <em>Game Track</em> built entirely in SwiftUI:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1Kwr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa595d439-f662-4d22-9e1f-a3faaa8edf35_5536x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1Kwr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa595d439-f662-4d22-9e1f-a3faaa8edf35_5536x2716.png 424w, https://substackcdn.com/image/fetch/$s_!1Kwr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa595d439-f662-4d22-9e1f-a3faaa8edf35_5536x2716.png 848w, https://substackcdn.com/image/fetch/$s_!1Kwr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa595d439-f662-4d22-9e1f-a3faaa8edf35_5536x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!1Kwr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa595d439-f662-4d22-9e1f-a3faaa8edf35_5536x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1Kwr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa595d439-f662-4d22-9e1f-a3faaa8edf35_5536x2716.png" width="1456" height="714" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a595d439-f662-4d22-9e1f-a3faaa8edf35_5536x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:714,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4293251,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1Kwr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa595d439-f662-4d22-9e1f-a3faaa8edf35_5536x2716.png 424w, https://substackcdn.com/image/fetch/$s_!1Kwr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa595d439-f662-4d22-9e1f-a3faaa8edf35_5536x2716.png 848w, https://substackcdn.com/image/fetch/$s_!1Kwr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa595d439-f662-4d22-9e1f-a3faaa8edf35_5536x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!1Kwr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa595d439-f662-4d22-9e1f-a3faaa8edf35_5536x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The first major change was to switch to IGDB for the game database. They have a vast array of games and are now owned by Twitch which should keep it running for a long time. It&#8217;s easy to add lesser known games<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> and whilst their <a href="https://api-docs.igdb.com/">API is a bit weird</a> it does provide some nice 3:4 artwork. It also provides a list of platforms for each game so I can now show a much smaller list; in fact, this page is bypassed entirely if the game is only available on a single platform!</p><p>The next change was to alter the way in which logs were being stored, specifically the automatic Steam logs. Previously we had a table named <em>users_to_games</em> which held a running total of hours spent on a conjoined user / game / platform that we could also use to hide a game from the list (i.e. dev tools, early access, etc). Any update to this would use a MySQL trigger to then insert a single record into a <em>logs</em> table. Everything was done in 0.1 hour increments as historically that was how Steam reported time played but that has now changed and it is broken down in minutes for Windows, Mac, and Linux. I have created a <em>steam_cache</em> table which stores these properties and also converts from a Steam ID to an IGDB ID via the <a href="https://api-docs.igdb.com/#external-game">External Game endpoint</a>. With this in place I can now split what was previously PC into PC, Mac, and Steam Deck<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>. I will also be able to create our own logs in minute increments rather than the previously used 6 minute increments.</p><p>Finally, rather than starting with the search screen I now show the last 15 games that were logged manually along with their platforms. Tapping one of these instantly brings up the &#8220;Add Log&#8221; screen and serves as a much quicker replacement for the previous deep linking provided by SiriKit.</p><p>These changes alone were a big improvement and formed the basis of v1.0 of the app which John and I started using at the start of the year. However, I wanted to add a bit more and took the logs page we previously had on a website and embedded it natively into the app:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TAeN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d1448e5-4e1a-4055-ab0e-9b8a74ec59e5_2738x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TAeN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d1448e5-4e1a-4055-ab0e-9b8a74ec59e5_2738x2716.png 424w, https://substackcdn.com/image/fetch/$s_!TAeN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d1448e5-4e1a-4055-ab0e-9b8a74ec59e5_2738x2716.png 848w, https://substackcdn.com/image/fetch/$s_!TAeN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d1448e5-4e1a-4055-ab0e-9b8a74ec59e5_2738x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!TAeN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d1448e5-4e1a-4055-ab0e-9b8a74ec59e5_2738x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TAeN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d1448e5-4e1a-4055-ab0e-9b8a74ec59e5_2738x2716.png" width="1456" height="1444" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9d1448e5-4e1a-4055-ab0e-9b8a74ec59e5_2738x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1444,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1618974,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TAeN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d1448e5-4e1a-4055-ab0e-9b8a74ec59e5_2738x2716.png 424w, https://substackcdn.com/image/fetch/$s_!TAeN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d1448e5-4e1a-4055-ab0e-9b8a74ec59e5_2738x2716.png 848w, https://substackcdn.com/image/fetch/$s_!TAeN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d1448e5-4e1a-4055-ab0e-9b8a74ec59e5_2738x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!TAeN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d1448e5-4e1a-4055-ab0e-9b8a74ec59e5_2738x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Originally I had server endpoints to fetch our latest 15 games for the Recent tab and the last 3 months of data for the Logs tab. This was a bit slow to load and only became slower when I tweaked the &#8220;Add Log&#8221; page to show a breakdown of times for each platform (which the eagle eyed of you may have noticed in the previous screenshots). My problem was that I was performing a complex subquery when fetching all of the logs; the solution was to eschew that altogether and instead just return the 5000 or so raw logs (which are fetched very quickly) and have the app join them together. </p><p>The result is that I can now show logs from 1st Jan 2018 with no visible slowdown all based on a network request that takes less than a second to resolve. These logs are all stored in a local <a href="https://realm.io/realm-swift/">Realm database</a> which is incredibly fast. I have not added the ability to add logs whilst offline but it would likely be trivial to do so as I have all of the building blocks in place.</p><p>One little extra I wanted to add was the ability to &#8220;tag&#8221; certain logs. Originally this was so we could distinguish games that were played on Steam Deck but I also wanted to use it for tracking what we&#8217;d played in VR and on the Game Pass and Apple Arcade services.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-ILe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb45e3fd2-03db-4ac2-bda9-6f28238218df_5536x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-ILe!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb45e3fd2-03db-4ac2-bda9-6f28238218df_5536x2716.png 424w, https://substackcdn.com/image/fetch/$s_!-ILe!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb45e3fd2-03db-4ac2-bda9-6f28238218df_5536x2716.png 848w, https://substackcdn.com/image/fetch/$s_!-ILe!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb45e3fd2-03db-4ac2-bda9-6f28238218df_5536x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!-ILe!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb45e3fd2-03db-4ac2-bda9-6f28238218df_5536x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-ILe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb45e3fd2-03db-4ac2-bda9-6f28238218df_5536x2716.png" width="1456" height="714" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b45e3fd2-03db-4ac2-bda9-6f28238218df_5536x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:714,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4121745,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-ILe!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb45e3fd2-03db-4ac2-bda9-6f28238218df_5536x2716.png 424w, https://substackcdn.com/image/fetch/$s_!-ILe!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb45e3fd2-03db-4ac2-bda9-6f28238218df_5536x2716.png 848w, https://substackcdn.com/image/fetch/$s_!-ILe!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb45e3fd2-03db-4ac2-bda9-6f28238218df_5536x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!-ILe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb45e3fd2-03db-4ac2-bda9-6f28238218df_5536x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Long pressing on a log brings up a context menu which has a list of tags. Interacting with this will let you choose if this is for just this log, this and all future logs, or every log. The tag is then applied on the server (and a note made if it needs to be automatically added for this game / platform combo in future) and the list is reloaded.</p><p>The only other thing to note about this page is one of the other context menu options; &#8220;Comment&#8221;. This goes back to what I was saying in this issue&#8217;s introduction about wanting to try new technologies, in this case a new API in iOS 16 called <a href="https://developer.apple.com/documentation/sharedwithyou/">Shared With You</a>. I&#8217;d seen that there was a way to link a conversation in Messages back to an item in an app and thought it would be fun to implement that as John and I will often message each other if we see something interesting in the others play history.</p><p>The first step was to add Universal Links to the app. The mechanism for this is relatively straightforward but the key thing would be that I&#8217;d need a webpage for each log. The logs themselves have unique identifiers but as multiple logs for a single game in the same day are merged together that wasn&#8217;t guaranteed to be stable; instead, I came up with a defined hash that looks like this:</p><p><em>1_2023-01-10_151974_381</em></p><p>The hash is broken up by underscores leaving four components; user ID, date, game ID, platform ID. With that, I can fetch the relevant compounded logs for that game for the day and generate <a href="https://gametrack.dodoapps.io/logs/1_2023-01-10_151974_381/">a basic website</a> which just spits out a sentence and an image URL into some OpenGraph headers. This URL is then loaded into an <em>MFMessageComposeViewController</em> and will appear as a nice preview once sent:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mOVB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c05e885-0e93-4913-a8ff-062e886d591d_772x1241.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mOVB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c05e885-0e93-4913-a8ff-062e886d591d_772x1241.jpeg 424w, https://substackcdn.com/image/fetch/$s_!mOVB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c05e885-0e93-4913-a8ff-062e886d591d_772x1241.jpeg 848w, https://substackcdn.com/image/fetch/$s_!mOVB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c05e885-0e93-4913-a8ff-062e886d591d_772x1241.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!mOVB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c05e885-0e93-4913-a8ff-062e886d591d_772x1241.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mOVB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c05e885-0e93-4913-a8ff-062e886d591d_772x1241.jpeg" width="226" height="363.2979274611399" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9c05e885-0e93-4913-a8ff-062e886d591d_772x1241.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1241,&quot;width&quot;:772,&quot;resizeWidth&quot;:226,&quot;bytes&quot;:151555,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!mOVB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c05e885-0e93-4913-a8ff-062e886d591d_772x1241.jpeg 424w, https://substackcdn.com/image/fetch/$s_!mOVB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c05e885-0e93-4913-a8ff-062e886d591d_772x1241.jpeg 848w, https://substackcdn.com/image/fetch/$s_!mOVB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c05e885-0e93-4913-a8ff-062e886d591d_772x1241.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!mOVB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c05e885-0e93-4913-a8ff-062e886d591d_772x1241.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>When tapped, this opens the app but the real magic is that it creates an <a href="https://developer.apple.com/documentation/sharedwithyou/swhighlight?changes=lat__5">SWHighlight</a> which can then be rendered in the app to show that this particular log was talked about in Messages:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LaMb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff79373-ea52-47af-bcd5-e3fbd43aff6a_1289x637.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LaMb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff79373-ea52-47af-bcd5-e3fbd43aff6a_1289x637.jpeg 424w, https://substackcdn.com/image/fetch/$s_!LaMb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff79373-ea52-47af-bcd5-e3fbd43aff6a_1289x637.jpeg 848w, https://substackcdn.com/image/fetch/$s_!LaMb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff79373-ea52-47af-bcd5-e3fbd43aff6a_1289x637.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!LaMb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff79373-ea52-47af-bcd5-e3fbd43aff6a_1289x637.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LaMb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff79373-ea52-47af-bcd5-e3fbd43aff6a_1289x637.jpeg" width="1289" height="637" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1ff79373-ea52-47af-bcd5-e3fbd43aff6a_1289x637.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:637,&quot;width&quot;:1289,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:101413,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LaMb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff79373-ea52-47af-bcd5-e3fbd43aff6a_1289x637.jpeg 424w, https://substackcdn.com/image/fetch/$s_!LaMb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff79373-ea52-47af-bcd5-e3fbd43aff6a_1289x637.jpeg 848w, https://substackcdn.com/image/fetch/$s_!LaMb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff79373-ea52-47af-bcd5-e3fbd43aff6a_1289x637.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!LaMb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ff79373-ea52-47af-bcd5-e3fbd43aff6a_1289x637.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Tapping that badge (which is a <a href="https://developer.apple.com/documentation/sharedwithyou/swattributionview?changes=lat__5">SWAttributionView</a>) opens up Messages directly to that point in the conversation so I can easily go back and see the discussion around that game.</p><p>Admittedly this isn&#8217;t terribly useful and there were a lot of hoops to jump through to get it to work. Specifically, we had to each enable &#8220;Shared With You&#8221; for this app within the settings app; for some reason, 3rd party apps are disabled by default and there is no way to provide a system permission prompt in the same way you can for push notifications or calendar access. Instead, you&#8217;d need to provide some kind of messaging within the app itself to tell users how to enable this feature like you do for keyboards or Safari extensions. Beyond that, Apple decides when a link is worth suggesting by the Shared With You system so it isn&#8217;t guaranteed that one of these links will appear.</p><p>That said, I do like the quick link back to a conversation and this was mostly done to satiate my curiosity around the feature rather than something I&#8217;ll actively use. [update: whilst proof reading this I clicked on the <a href="https://gametrack.dodoapps.io/logs/1_2023-01-10_151974_381/">test link</a> and found that I get the little <a href="https://bendodson.s3-eu-west-1.amazonaws.com/weblog/2022/attribution-messages-on-macos.png">attribution</a> back to Messages whilst viewing the webpage in Safari on my Mac Studio - that&#8217;s pretty cool!]</p><p>Speaking of learning new technologies, the final tab &#8211;&nbsp;&#8220;Stats&#8221; &#8211; was created primarily so I could learn how to use <a href="https://developer.apple.com/documentation/charts">Swift Charts</a>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GhXs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75e228a8-5384-4501-9d72-44a832c7f757_4137x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GhXs!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75e228a8-5384-4501-9d72-44a832c7f757_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!GhXs!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75e228a8-5384-4501-9d72-44a832c7f757_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!GhXs!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75e228a8-5384-4501-9d72-44a832c7f757_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!GhXs!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75e228a8-5384-4501-9d72-44a832c7f757_4137x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GhXs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75e228a8-5384-4501-9d72-44a832c7f757_4137x2716.png" width="1456" height="956" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/75e228a8-5384-4501-9d72-44a832c7f757_4137x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:956,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2018116,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GhXs!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75e228a8-5384-4501-9d72-44a832c7f757_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!GhXs!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75e228a8-5384-4501-9d72-44a832c7f757_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!GhXs!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75e228a8-5384-4501-9d72-44a832c7f757_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!GhXs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75e228a8-5384-4501-9d72-44a832c7f757_4137x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>For the past few years I&#8217;ve pulled out a number of stats from the MySQL database and shared them with John so we could compare our most played games and total hours. As the app has all of our log files stored in a local Realm database, it&#8217;s easy to pull out a ton of stats which can then be displayed in the app instead!</p><p>For each month I have the total hours and number of games, then the top 5 most played along with a link to view every game. Beneath that there are two sets of graphs: a line graph to show hours &amp; games played for the whole year and a bar chart to show hours &amp; games played broken down by platform. I&#8217;ll likely extend this in future to show how much time has been spent on subscription services such as Game Pass thanks to the tagging system I mentioned previously.</p><p>Whilst monthly breakdowns are nice, I also added some UI to change this to a yearly breakdown:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ldtX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44b12cf3-f13b-4b38-bbd2-6cf60c3684c8_4137x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ldtX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44b12cf3-f13b-4b38-bbd2-6cf60c3684c8_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!ldtX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44b12cf3-f13b-4b38-bbd2-6cf60c3684c8_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!ldtX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44b12cf3-f13b-4b38-bbd2-6cf60c3684c8_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!ldtX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44b12cf3-f13b-4b38-bbd2-6cf60c3684c8_4137x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ldtX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44b12cf3-f13b-4b38-bbd2-6cf60c3684c8_4137x2716.png" width="1456" height="956" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/44b12cf3-f13b-4b38-bbd2-6cf60c3684c8_4137x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:956,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1435816,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ldtX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44b12cf3-f13b-4b38-bbd2-6cf60c3684c8_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!ldtX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44b12cf3-f13b-4b38-bbd2-6cf60c3684c8_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!ldtX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44b12cf3-f13b-4b38-bbd2-6cf60c3684c8_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!ldtX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44b12cf3-f13b-4b38-bbd2-6cf60c3684c8_4137x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This was a really fun interface to build in SwiftUI as it came together <em>so</em> <em>quickly</em>. I started by building the panel at the top that let you go backwards and forwards through months and it was done within 5 minutes (including bounding so you couldn&#8217;t go beyond the current month or earlier than 2018). I then added the tap to switch between Month / Year and to choose a specific date, which again only took a few minutes to implement.</p><p>With that in place, I could then create a date range which would either be something like &#8220;2020&#8221; or &#8220;2023-01&#8221;. This could then be used as a filter for the logs by checking if the date string was prefixed with that value<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>; if it was, then it was a log that should be used in the stats page.</p><p>The Swift Charts API was also very nice to work with and super fast to implement. Consider a struct like this:</p><pre><code>struct PlatformStats: Identifiable {
    var id: Int {
        return platform.id
    }
    let platform: Platform
    let totalMinutes: Int
    let totalGames: Int
    
    var totalHours: Int {
        return Int((Double(totalMinutes) / 60).rounded(.up))
    }
}</code></pre><p>If we have an array of those as a variable named <code>&#8217;platforms&#8217;</code> then the only code required to generate the bar chart is:</p><pre><code>Chart(platforms) {
    switch mode {
    case .games:
        BarMark(
            x: .value("Platforms", $0.platform.name),
            y: .value("Games", $0.totalGames)
        )
    case .hours:
        BarMark(
            x: .value("Platforms", $0.platform.name),
            y: .value("Hours", $0.totalHours)
        )
    }
}
.colorScheme(.dark)</code></pre><p>Crazy!</p><p>However, great simplicity comes <em>without</em> great customisation. The most annoying facet in this first version of Swift Charts is that you can&#8217;t change the colour of the axis or their labels. They&#8217;ll always use the secondary label colour of the current theme so when I placed it in a black box the axis all but disappeared. To fix this, I had to use a deprecated API to tell the chart to render as if the user was always using dark mode &#129318;&#127995;&#8205;&#9794;&#65039;</p><p>It is still a good set of APIs though and works out of the box with very simple data structures. It&#8217;s unlikely I&#8217;ll be able to use it in any upcoming client projects due to it&#8217;s iOS 16 minimum requirement but it&#8217;s definitely something I&#8217;ll be keeping an eye on for the future.</p><p>So, that&#8217;s <em>Game Track</em>. I&#8217;ve spent far too long working on it bearing in mind it&#8217;s just for tracking some basic stats that likely don&#8217;t really need tracking but it has been fun to make the previous app more functional and a lot prettier. I not only upped my SwiftUI game but also got to try out some new iOS 16 technologies as well, something that will hopefully pay dividends in the future.</p><h2>Back Seat Shuffle</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xeiI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c02d599-e590-42b5-8f44-fb1c66178e12_800x559.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xeiI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c02d599-e590-42b5-8f44-fb1c66178e12_800x559.gif 424w, https://substackcdn.com/image/fetch/$s_!xeiI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c02d599-e590-42b5-8f44-fb1c66178e12_800x559.gif 848w, https://substackcdn.com/image/fetch/$s_!xeiI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c02d599-e590-42b5-8f44-fb1c66178e12_800x559.gif 1272w, https://substackcdn.com/image/fetch/$s_!xeiI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c02d599-e590-42b5-8f44-fb1c66178e12_800x559.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xeiI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c02d599-e590-42b5-8f44-fb1c66178e12_800x559.gif" width="800" height="559" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6c02d599-e590-42b5-8f44-fb1c66178e12_800x559.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:559,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3783863,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xeiI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c02d599-e590-42b5-8f44-fb1c66178e12_800x559.gif 424w, https://substackcdn.com/image/fetch/$s_!xeiI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c02d599-e590-42b5-8f44-fb1c66178e12_800x559.gif 848w, https://substackcdn.com/image/fetch/$s_!xeiI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c02d599-e590-42b5-8f44-fb1c66178e12_800x559.gif 1272w, https://substackcdn.com/image/fetch/$s_!xeiI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c02d599-e590-42b5-8f44-fb1c66178e12_800x559.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><a href="https://www.thedododeveloper.com/p/3-yule-logging">Last issue</a> I mentioned an idea I&#8217;d had for an app that would let you choose a folder on an external drive and have it randomly play all the videos in that directory. The primary use case for this was for an iPad mounted on the back of my car seat in order to entertain the children on long drives.</p><p>The app, <em>Back Seat Shuffle</em>, is now complete! It works by presenting a file selection panel on launch and letting you navigate to the directory you want; in my case it is always an external USB drive but this will work with local files or even cloud-based services if you&#8217;re connected to the internet. Once you&#8217;ve chosen a folder, I strip out any files that aren&#8217;t videos and then play them in a random order. Once all the videos have played the window is dismissed and you&#8217;re back to the file picker.</p><p>It&#8217;s very basic, but it solves a genuine need I have as most apps either don&#8217;t have randomised autoplaying or don&#8217;t have the ability to read external drives without copying the contents locally.</p><p>As promised last time, the app code is <a href="https://github.com/bendodson/back-seat-shuffle">available on GitHub</a> so you can compile a copy for yourself. Whilst I could potentially put it on the App Store, this is one of those weird edge cases that likely wouldn&#8217;t get through App Review as they&#8217;ll look at it and say &#8220;this is just the Files app&#8221; and completely miss the point. As this would be a free app, it&#8217;s not worth the hassle of doing screenshots, App Store description, etc, only to have it be rejected by App Review.</p><p>Of course, if enough of you want to try it out then that would likely change my mind&#8230;</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://github.com/bendodson/back-seat-shuffle" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!09Hk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54ddbfa2-1bc4-4ab1-be01-d659a5f4bb46_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!09Hk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54ddbfa2-1bc4-4ab1-be01-d659a5f4bb46_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!09Hk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54ddbfa2-1bc4-4ab1-be01-d659a5f4bb46_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!09Hk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54ddbfa2-1bc4-4ab1-be01-d659a5f4bb46_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!09Hk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54ddbfa2-1bc4-4ab1-be01-d659a5f4bb46_1024x1024.png" width="168" height="168" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/54ddbfa2-1bc4-4ab1-be01-d659a5f4bb46_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:168,&quot;bytes&quot;:809276,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://github.com/bendodson/back-seat-shuffle&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!09Hk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54ddbfa2-1bc4-4ab1-be01-d659a5f4bb46_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!09Hk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54ddbfa2-1bc4-4ab1-be01-d659a5f4bb46_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!09Hk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54ddbfa2-1bc4-4ab1-be01-d659a5f4bb46_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!09Hk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54ddbfa2-1bc4-4ab1-be01-d659a5f4bb46_1024x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://github.com/bendodson/back-seat-shuffle&quot;,&quot;text&quot;:&quot;View Back Seat Shuffle on GitHub&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://github.com/bendodson/back-seat-shuffle"><span>View Back Seat Shuffle on GitHub</span></a></p><p></p><h2>Bookmark</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!K2mI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369067e3-e5f8-4662-8ac1-262fda12a6da_600x1300.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!K2mI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369067e3-e5f8-4662-8ac1-262fda12a6da_600x1300.gif 424w, https://substackcdn.com/image/fetch/$s_!K2mI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369067e3-e5f8-4662-8ac1-262fda12a6da_600x1300.gif 848w, https://substackcdn.com/image/fetch/$s_!K2mI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369067e3-e5f8-4662-8ac1-262fda12a6da_600x1300.gif 1272w, https://substackcdn.com/image/fetch/$s_!K2mI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369067e3-e5f8-4662-8ac1-262fda12a6da_600x1300.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!K2mI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369067e3-e5f8-4662-8ac1-262fda12a6da_600x1300.gif" width="244" height="528.6666666666666" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/369067e3-e5f8-4662-8ac1-262fda12a6da_600x1300.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1300,&quot;width&quot;:600,&quot;resizeWidth&quot;:244,&quot;bytes&quot;:938054,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!K2mI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369067e3-e5f8-4662-8ac1-262fda12a6da_600x1300.gif 424w, https://substackcdn.com/image/fetch/$s_!K2mI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369067e3-e5f8-4662-8ac1-262fda12a6da_600x1300.gif 848w, https://substackcdn.com/image/fetch/$s_!K2mI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369067e3-e5f8-4662-8ac1-262fda12a6da_600x1300.gif 1272w, https://substackcdn.com/image/fetch/$s_!K2mI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F369067e3-e5f8-4662-8ac1-262fda12a6da_600x1300.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Another one from the <a href="https://www.thedododeveloper.com/p/3-yule-logging">last issue</a>, Bookmark is my app for tracking what I read. One of the issues when it comes to tracking things is that you can easily forget to start or stop a timer. This happened to me last week when I fell asleep whilst reading in bed&#8230;</p><p>This led me to an idea &#128161;. If I know how much I&#8217;ve read previously and how long it took, and I know where I am now, then I should be able to <em>predict</em> how long the last reading session took&#8230;</p><p><em>((current position / previous position) * duration so far) - duration so far</em></p><p>I&#8217;ve updated the app so now when I add a position that was later than the previous position, this formula is used and the duration is automatically predicted. This works great as it can help me if I forget to start a reading timer but it can also save me time when entering my logs (as long as I haven&#8217;t been slower or faster than usual).</p><h2>Apple Music Artwork Finder updates</h2><p>A few minor updates to the <a href="https://bendodson.com/projects/apple-music-artwork-finder/">Apple Music Artwork Finder</a>:</p><ol><li><p>You can now search for curator artwork. These are typically logos for Apple Music radio series.<br><em>Example: <a href="https://music.apple.com/gb/curator/rocket-hour/993269779">Rocket Hour</a></em></p></li><li><p>Playlist artwork now has an option for a high resolution PNG in addition to the previous high resolution JPEG. It looks like this is the original image that is used so isn&#8217;t compressed by the server in any way.<br><em>Example: <a href="https://music.apple.com/gb/playlist/antidote/pl.2e5ee4612fd246529e9e34d23549dc98">Antidote</a></em></p></li><li><p>It turns out it was possible to get music video artwork in the artwork finder but it wasn&#8217;t rendering at the correct sizes. I&#8217;ve fixed this so music videos are now officially supported.<br><em>Example: <a href="https://music.apple.com/gb/music-video/cake-by-the-ocean/1444865551">Cake by the Ocean</a></em></p></li></ol><h2>Music Library Tracker v2.0</h2><p>I&#8217;m still working away on the big v2.0 update for <a href="https://itunes.apple.com/us/app/music-tracker-automatic-tracking/id1062583712?ls=1&amp;mt=8">Music Library Tracker</a>. My plan is to have it ready for release on the <strong>31st January</strong> so I&#8217;m expecting to send out another beta update next week.</p><p>I won&#8217;t say anything else at this point as I expect I will go into everything in a lot more detail in the next issue.</p><p>If you have not yet tried out the Spatial Audio update, you can use the button below to download the beta for free.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://testflight.apple.com/join/uKS7gUcm" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Vy_W!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c7fdf8-f188-47aa-81b6-02a31cc6c98d_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!Vy_W!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c7fdf8-f188-47aa-81b6-02a31cc6c98d_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!Vy_W!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c7fdf8-f188-47aa-81b6-02a31cc6c98d_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!Vy_W!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c7fdf8-f188-47aa-81b6-02a31cc6c98d_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Vy_W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c7fdf8-f188-47aa-81b6-02a31cc6c98d_1024x1024.png" width="156" height="156" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e6c7fdf8-f188-47aa-81b6-02a31cc6c98d_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:156,&quot;bytes&quot;:209695,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://testflight.apple.com/join/uKS7gUcm&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Vy_W!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c7fdf8-f188-47aa-81b6-02a31cc6c98d_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!Vy_W!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c7fdf8-f188-47aa-81b6-02a31cc6c98d_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!Vy_W!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c7fdf8-f188-47aa-81b6-02a31cc6c98d_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!Vy_W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c7fdf8-f188-47aa-81b6-02a31cc6c98d_1024x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://testflight.apple.com/join/uKS7gUcm&quot;,&quot;text&quot;:&quot;Join TestFlight beta&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://testflight.apple.com/join/uKS7gUcm"><span>Join TestFlight beta</span></a></p><p><em>* Music Library Tracker v2.0 will run on any iOS device running iOS 13 or greater. If you already have Music Library Tracker installed then this will update that version; you won&#8217;t be able to downgrade. This beta will expire when the app update is publicly released later this month.</em></p><p>I would greatly appreciate any feedback you may have, especially if you run into any problems with the Spatial Audio matching. You can always comment on this post, provide feedback directly through TestFlight, or email me via <a href="mailto:ben@bendodson.com">ben@bendodson.com</a>.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/4-new-year-new-tracking-app/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/4-new-year-new-tracking-app/comments"><span>Leave a comment</span></a></p><p><em>Also, feel free to publicly share any screenshots, videos, or thoughts on the app update (although I&#8217;d obviously prefer you share any issues you encounter with me first!)</em></p><h2>My published apps in 2022</h2><p>When I did my wrap up of 2022 in the last issue I completely forgot to promote my own apps &#129318;&#127995;&#8205;&#9794;&#65039;. So, let&#8217;s do that now!</p><p>I released 3 new apps last year:</p><ul><li><p><strong><a href="https://apps.apple.com/us/app/browser-note/id1613527249">Browser Note</a></strong><br>A simple way to add a pause to your browsing with dismissible reminders or notes blocking you from certain sites (i.e. social media).</p></li><li><p><strong><a href="https://apps.apple.com/us/app/chaise-longue-to-5k/id1633434180">Chaise Longue to 5K: Because couches are so common</a></strong><br>Use other apps whilst monitoring your Couch-to-5K run with exclusive Picture-in-Picture support. Perfect for a treadmill in front of an Apple TV.</p></li><li><p><strong><a href="https://apps.apple.com/us/app/return-to-dark-tower-assistant/id1659406816">Return to Dark Tower Assistant</a></strong></p><p>Keep track of your phases, actions, cards, and Advantages in Return to Dark Tower. Optimised for iPad Slide Over.</p></li></ul><p>I was particularly pleased with both <em>Browser Note</em> and <em>Chaise Longue to 5K</em> as they both run on iPhone, iPad, and Mac with my first foray into Catalyst development! <em>Chaise Longue to 5K</em> goes a &#8216;step&#8217; further (ha!) and runs on Apple TV as well.</p><p>This year I want to release at least 3 new apps which is convenient as I have 3 apps that are close to the end of development: <em>Playlist Precis</em>, <em>Board Game Lists</em>, and <em>Arru</em>. You&#8217;ll be hearing about those soon! I also have an Ink framework and some games I&#8217;d like to get released so lots to look forward to.</p><h2>Recommended Links</h2><h4>Video Games</h4><ul><li><p><a href="https://order.wreckfestgame.com">Wreckfest</a> - I saw this whilst browsing through the Game Pass listings and thought it might be a good game to play with my 4 year old son. It turns out I was right! It&#8217;s essentially a demolition derby / banger racing sim with a good variety of challenges and some silliness. I thought my son was going to die laughing when we had a demolition derby with combine harvesters. It&#8217;s actually an Xbox One game but still looks great on the Series X.</p></li><li><p><a href="https://tunicgame.com">Tunic</a> - This featured heavily in &#8220;Game of the Year&#8221; lists last month and deservedly so. It looks like a cross between Zelda and <em>Death&#8217;s Door</em> but the unique mechanic is that you uncover parts of the instruction manual as you play. It&#8217;s all written in a strange language so you have to try and work out what is going on yourself, similar to importing Japanese games back in the &#8216;90s. One clever twist is that most of the things in the manual are available immediately, you just don&#8217;t know how to trigger them. A lot of fun but I&#8217;d definitely suggest turning down the combat difficulty.</p></li></ul><h4>Board Games</h4><ul><li><p><a href="https://boardgamegeek.com/boardgame/338468/paperback-adventures">Paperback Adventures</a> - I have no idea how I found this game as I&#8217;ve never played it&#8217;s predecessors, <em>Paperback</em> and <em>Hardback</em>. In any case, I ordered it and then upon realising you had to buy a character box for it as well ordered that and finally got up and running this week. It&#8217;s a solo-player roguelike in which you battle enemies by spelling words. There is a very interesting &#8220;splaying&#8221; mechanic using the two edges of your letter cards to determine hits and blocks. I like it very much! Just be sure that you buy one of the three character boxes along with the core box as you do need both parts!</p></li></ul><h4>TV Shows</h4><ul><li><p><a href="https://tv.apple.com/gb/show/slow-horses/umc.cmc.2szz3fdt71tl1ulnbp8utgq5o">Slow Horses</a> - This has been on my &#8220;to watch&#8221; list for a while but the number of people that mentioned it in &#8220;best of 2022&#8221; lists caused me to binge both seasons in 3 nights. It&#8217;s excellent. The premise is that there are a bunch of useless MI5 agents who get given the bad jobs rather than being fired but they somehow manage to get tangled up in a real case. An excellent thriller with some hilarious writing for Gary Oldman&#8217;s character. I also got a big belly laugh out of River Cartwright saying &#8220;not again&#8221; in Season 2 (keeping that as spoiler-free as possible).</p></li><li><p><a href="https://www.hbo.com/the-white-lotus">The White Lotus</a> - Another one that scored highly in &#8220;best of 2022&#8221; lists, this follows a number of wealthy guests in a Hawaiian resort as somebody gets murdered. I don&#8217;t know much more than that as I&#8217;ve only watched a couple of episodes but it is excellent so far! I&#8217;m particularly looking forward to season 2 which stars F. Murray Abraham and Aubrey Plaza.</p></li></ul><h2>Roadmap</h2><p>The roadmap is my way of committing to what I&#8217;m going to do over the next 2 weeks.</p><h4>5th - 18th January</h4><ul><li><p>Keep working on <em>Music Library Tracker v2.0</em> including some UI changes on iPad &#9989;</p></li><li><p>Added a Stats page to <em>Game Track</em> with Swift Charts. Also used a new iOS 16 API (&#8220;Shared With You&#8221;) &#9989;</p></li><li><p>Finished working on <em>Back Seat Shuffle</em> and made the code public on GitHub &#9989;</p></li></ul><p>Another fortnight with everything done! Next issue there is only one thing I want to achieve, but it&#8217;s a big one:</p><h4>19th January - 1st February (Issue #5)</h4><ul><li><p>Release <em>Music Library Tracker v2.0</em></p></li></ul><p>That wraps it up for this issue. I hope you found something of interest and that you&#8217;ll be able to recommend the newsletter to your friends, family, and colleagues. You can always comment on this issue or by emailing me directly via <a href="mailto:ben@bendodson.com">ben@bendodson.com</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/subscribe?"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/4-new-year-new-tracking-app/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/4-new-year-new-tracking-app/comments"><span>Leave a comment</span></a></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>One such piece of code was a Tinder-style swiping mechanic I built for an as-yet-unreleased app called <em>Playlist Precis</em> (you&#8217;ll hear about it soon!) which I was then able to use in it&#8217;s entirety in a job hunting app for a client.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>iOS 16 did fix this by bubbling them up to showing under the search bar when it was empty but the link had stopped working for some reason; no idea why as if you searched and tapped on one it would work so some kind of Spotlight bug.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>A good example is <a href="https://www.igdb.com/games/illustrated">Illustrated</a> which I added to IGDB this morning! The only other games I&#8217;ve had to add are some side-loaded Playdate titles.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>Neither John or myself play games on Linux so if there are any minutes linked to Linux from the Steam API then that means it was a Steam Deck (which is Linux based).</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>If you&#8217;re not a programmer then you&#8217;re likely not aware that most dates are stored in reverse order i.e. 2023-01-16. This gets around localisation issues (*cough* America *cough*) but also means you can sort in ascending or descending order incredibly easily. It was also very nice for me filtering by month or year as you can just grab the prefix, a fast action in a filter.</p></div></div>]]></content:encoded></item><item><title><![CDATA[#3: Yule Logging]]></title><description><![CDATA[Bookmark, Back Seat Shuffle, and my end of year recommendations]]></description><link>https://www.thedododeveloper.com/p/3-yule-logging</link><guid isPermaLink="false">https://www.thedododeveloper.com/p/3-yule-logging</guid><dc:creator><![CDATA[Ben Dodson]]></dc:creator><pubDate>Wed, 04 Jan 2023 14:45:29 GMT</pubDate><enclosure url="https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/da57094c-7763-4fb5-ba5a-c8c3d72b1761_1920x1080.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dear Reader,</p><p>Firstly my apologies that this issue has landed in your inbox slightly later than scheduled. I was ill just before the holidays and then my family were ill during them so I didn&#8217;t get the writing time I&#8217;d initially envisioned before finishing for Christmas! However, I still managed to work on a number of different projects over the festive period including a further update to the <em>Music Library Tracker</em>, an update to one of my personal logging apps for the books I&#8217;m reading, an overhaul of an app I built to track mine and a friends gaming time, and a new iPad app idea to entertain my children in the car (and possibly save my wife from being propelled through my windscreen).</p><p>Unfortunately during the collation of this issue it became clear that it was becoming a bit too lengthy even for me so I&#8217;ve left out the discussion around my <em>Game Track</em> app. Look out for that in the next issue as I still have a few features to add!</p><p>As a bonus (maybe), I&#8217;ve put together some end of year recommendations based on the games I&#8217;ve played and various media I&#8217;ve consumed throughout 2022.</p><p>I hope all of you had a wonderful Christmas and were able to get a decent amount of downtime or time to work on your own projects depending on your own preferences. I personally can&#8217;t stand to not do any coding across the holidays &#8211;&nbsp;I literally feel withdrawal after a few days &#8211; but I do try to only work on smaller fun projects rather than doing anything &#8220;serious&#8221;.</p><p>Finally, many thanks to all of you who picked up a copy of <em><a href="https://dodoapps.io/return-to-dark-tower-assistant/">Return To Dark Tower Assistant</a></em>. I said in the <a href="https://www.thedododeveloper.com/p/2-music-library-tracker-v20">last issue</a> &#8220;it may only get single-digit downloads, but it&#8217;s an app I&#8217;m proud of&#8221;; happily, it&#8217;s close to breaking 100 downloads which was very unexpected. Thank you to all those that spread the word or got it for themselves.</p><p>I hope you&#8217;ll find something of interest in today&#8217;s issue and I look forward to hearing any feedback you may have.</p><p><strong>&#8212; Ben</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/3-yule-logging/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/3-yule-logging/comments"><span>Leave a comment</span></a></p><h2>Contents</h2><ul><li><p>Music Library Tracker v2.0 build 3</p></li><li><p>Bookmark, an app for logging reading time</p></li><li><p>Back Seat Shuffle, an iPad prototype</p></li><li><p>End of year recommendations</p></li><li><p>Roadmap</p></li></ul><h2>Music Library Tracker v2.0 build 3</h2><p>First of all, a big thank you to all of you who joined the <em>Music Library Tracker</em> TestFlight Beta from the <a href="https://www.thedododeveloper.com/p/2-music-library-tracker-v20">last issue</a> and to those of you who got in touch to report issues or provide other feedback. This is the first time in a long while that I&#8217;ve provided beta access to one of my projects so I&#8217;m very grateful to all of you who have made the process worth while.</p><p>Due to all of the feedback, there are a few issues that have come to light that I&#8217;ve been working to address.</p><ul><li><p><strong>Playlist generation</strong><br>It appears that some of the Spatial Audio BETA playlists are not containing all of the tracks that are listed within the Spatial Audio tab in the app. I&#8217;ve tweaked the way these playlists are generated so songs are added in batches which will hopefully alleviate the problem (I haven&#8217;t been able to replicate this as I only have a few hundred upgraded tracks, alas). If you go into the settings panel within the app, you&#8217;ll find a new button that lets you recreate your playlist so if you are affected by this issue please hit that button to see if it resolves the issue.</p></li><li><p><strong>Spatial Audio matching does not work on macOS<br></strong>To be honest, the macOS build was not something I&#8217;d planned but is instead a consequence of Apple&#8217;s move to it&#8217;s own CPUs (&#8220;Apple Silicon&#8221;). If you have one of their newer machines, you can install iOS apps and use them in a mode akin to an iPad simulator. I hadn&#8217;t paid this any mind as the APIs I use to access the media library do not exist on mac so I assumed this wouldn&#8217;t work but I&#8217;ve had people message in to say the app itself with it&#8217;s monitoring of your library does actually work on an Apple Silicon mac! However, this new Spatial Audio feature doesn&#8217;t work likely due to the APIs not returning the correct identifiers. I need to look into this in more detail but it looks likely that I&#8217;ll need to just hide the Spatial Audio tab on macOS if it doesn&#8217;t work there.</p></li><li><p><strong>Sort options within Spatial Audio tab</strong><br>At the moment the tracks are sorted by artist name, then album name, then track order but I&#8217;ve had a request to provide a few more sorting options in the same way that you can sort tracks in other areas of the app. I&#8217;ll add this in the next update.</p></li><li><p><strong>The app shows tracks that are no longer available in Spatial Audio<br></strong>This is a bit of a trickier one. I had naively assumed that if a track were upgrade to Spatial Audio then it would stay that way. To that end, the database I built will stop checking the details of a track that has been updated as it was a waste of server resources. It turns out that tracks <em>can</em> revert back to non-Spatial Audio issues; this is allegedly due to them being created via upmixing software which Apple does not allow (so they get onto Apple Music and then are pulled when Apple enforces it&#8217;s policy). I&#8217;m going to update my <a href="https://bendodson.com/projects/spatial-audio-finder/">Spatial Audio Finder</a> in the first instance so upgraded tracks can be reverted back to non-Spatial status but how to handle this in the app is a little trickier as I&#8217;ll need to always send your entire library for matching and then somehow flag when a track has been removed from the list (and remove it from the auto-generated playlist). My idea at the moment is that this is something that should likely appear within the regular &#8220;Activity&#8221; section of the app and would show as a change of the track metadata. In any case, it was too much to think about during the Christmas break so I shall put some thought to it before the next update!</p></li></ul><p>I think that is most of the feedback covered. As I mentioned, the tweak to the Apple Music playlist generation should be available for you now in TestFlight and will show as v2.0 build 3. I&#8217;ve also added an option within the settings panel to disable the Apple Music playlist should you not wish to have this feature (it is enabled by default).</p><p>If you have not yet tried out the update, you can use the button below to download the beta for free.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://testflight.apple.com/join/uKS7gUcm&quot;,&quot;text&quot;:&quot;Join TestFlight beta&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://testflight.apple.com/join/uKS7gUcm"><span>Join TestFlight beta</span></a></p><p><em>* Music Library Tracker v2.0 will run on any iOS device running iOS 13 or greater. If you already have Music Library Tracker installed then this will update that version; you won&#8217;t be able to downgrade. This beta will expire when the app update is publicly released in January.</em></p><p>I would greatly appreciate any feedback you may have, especially if you run into any problems with the Spatial Audio matching. You can always comment on this post, provide feedback directly through TestFlight, or email me via <a href="mailto:ben@bendodson.com">ben@bendodson.com</a>.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/3-yule-logging/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/3-yule-logging/comments"><span>Leave a comment</span></a></p><p><em>Also, feel free to publicly share any screenshots, videos, or thoughts on the app update (although I&#8217;d obviously prefer you share any issues you encounter with me first!)</em></p><h2>Bookmark</h2><p>I love tracking what I do. I don&#8217;t know why, but I get a sense of satisfaction in seeing what I&#8217;ve done over a particular time period (as you&#8217;ll see when you get to the recommendations in this issue). My current suite of tracking apps are:</p><ul><li><p>Video games - custom app named <em>Game Track</em></p></li><li><p>Board games - <a href="https://apps.apple.com/us/app/board-game-stats/id892542000">Board Game Stats</a></p></li><li><p>TV shows - <a href="https://apps.apple.com/gb/app/trakt/id1514873602">Trakt</a></p></li><li><p>Films - <a href="https://apps.apple.com/gb/app/trakt/id1514873602">Trakt</a></p></li><li><p>Books - custom app named <em>Bookmark</em></p></li><li><p>Projects - <a href="https://apps.apple.com/gb/app/timery-for-toggl/id1425368544">Timery for Toggl</a></p></li></ul><p>In addition I track a number of health metrics, mostly from the Apple Watch and a variety of apps that work with HealthKit.</p><p>A few years ago I built an app I named <em>Time Well Spent</em> for keeping track of the books I was reading, TV shows I was watching, and projects I was working on. The app was my first foray into SwiftUI and I also experimented with Swift as a backend service using Vapor on top of a Heroku instance. Whilst the app <em>worked</em>, I was never happy with the backend stuff as the code was all a bit of a mess and it would take around 10 minutes to push a code update to the cloud due to the number of dependencies, etc, that Vapor had. I&#8217;d also decided to use MySQL as that was what I was most familiar with and so the code ended up looking like this:</p><pre><code>return req.db.transaction { (db) -&gt; EventLoopFuture&lt;Book&gt; in
    return session.save(on: db).flatMap {
        return BookModel.query(on: db)
            .set(\.$remaining, to: progression.remaining)
            .set(\.$progress, to: progression.progress)
            .set(\.$editions, to: editions)
            .set(\.$status, to: book.status)
            .filter(\.$id == book.id)
            .update()
    }.transform(to: book)
}</code></pre><p>I had very little idea how that was working back when I wrote it and I have zero idea now! All those nested returns and weird maps &#129314;</p><p>The first thing to go was TV shows. I was manually adding artwork and metadata to my database to have them show up but I was never really happy with the process. I&#8217;d been using Trakt as a VIP member since 2012 for showing me the air dates of my shows so I have no idea why I wasn&#8217;t always using it to track the TV and film I was watching. When I realised I could hook it up to Plex so it would automatically tell <a href="https://trakt.tv/">Trakt</a> when I finished watching something, there was almost no need for an app at all (although I do now use the official <a href="https://apps.apple.com/gb/app/trakt/id1514873602">Trakt app</a> when watching stuff on Netflix, Disney Plus, etc).</p><p>The next thing to go was projects. I had started using <a href="https://apps.apple.com/gb/app/timery-for-toggl/id1425368544">Timery</a> to track how much time I was spending on client work. As all of that data was available via an API, it made sense for me to use the same process (<a href="https://bendodson.com/weblog/2022/12/08/stream-deck-ios-development/">using a Stream Deck</a>) to track when I was working on my own side projects. I wrote a PHP script that ran every day to fetch my Toggl data and store it in my own database for projects assigned to &#8220;Dodo Apps Ltd&#8221;.</p><p>That meant the app was now only used for tracking books. You may wonder why I haven&#8217;t moved to a 3rd party app for this? Well, I&#8217;ve just never found one I <em>liked</em>. The closest I came was using <a href="https://apps.apple.com/us/app/id1085047737?platform=iphone">Bookly</a> but I absolutely detested the UI. Other apps have come and gone but the main thing I wanted was a way to agnostically log my place in a book; this should not be done on &#8220;page number&#8221; as nearly every app does as a page might change. For example, on my iPad I might be on page 200 of 358 but on my iPhone that might be 780 of 1419. Page numbers can even change on the same device due to switching fonts part way through or even an iOS update or new piece of hardware changing the visible space. The other requirement was that I could log time against multiple versions of a book; this was usually when reading on both iPhone and iPad but I would occasionally have both the paperback and eBook version and alternate between them depending on my mood.</p><p>The <em>Time Well Spent</em> app I built addressed all of these issues (I&#8217;ll show you how shortly) but had a tab bar interface for Books, TV Shows and Projects. All I needed to do was remove those unnecessary tabs and then I&#8217;d have my book logging app. The only other minor issue was that I had recently bought a Kindle Paperwhite and wanted to add that as an option when I logged a book; that should be fine, it was just an enum in the project after all... except it wasn't; it was also an enum in the Vapor server app which meant I&#8217;d need to recompile that and the recent Xcode changes meant that was a far more taxing prospect than I was willing to make. I left it for a few months and just logged Kindle books as "paperback" but I finally put my foot down last week and rebuilt the server code in PHP. This saved me $7 a month on Heroku hosting<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> and let me add Kindle as a viable option within the app.</p><p>The app is now complete and rebranded as <em>Bookmark</em>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ke4F!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F995c6668-ff3a-4fa1-8a30-c4a9a6de744b_4137x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ke4F!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F995c6668-ff3a-4fa1-8a30-c4a9a6de744b_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!Ke4F!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F995c6668-ff3a-4fa1-8a30-c4a9a6de744b_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!Ke4F!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F995c6668-ff3a-4fa1-8a30-c4a9a6de744b_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!Ke4F!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F995c6668-ff3a-4fa1-8a30-c4a9a6de744b_4137x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ke4F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F995c6668-ff3a-4fa1-8a30-c4a9a6de744b_4137x2716.png" width="1456" height="956" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/995c6668-ff3a-4fa1-8a30-c4a9a6de744b_4137x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:956,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2771084,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ke4F!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F995c6668-ff3a-4fa1-8a30-c4a9a6de744b_4137x2716.png 424w, https://substackcdn.com/image/fetch/$s_!Ke4F!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F995c6668-ff3a-4fa1-8a30-c4a9a6de744b_4137x2716.png 848w, https://substackcdn.com/image/fetch/$s_!Ke4F!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F995c6668-ff3a-4fa1-8a30-c4a9a6de744b_4137x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!Ke4F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F995c6668-ff3a-4fa1-8a30-c4a9a6de744b_4137x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Bookmark, an app for logging time spent reading</figcaption></figure></div><p>The initial page lists the number of books I&#8217;m currently reading along with percentage completed and estimated time to finish reading. When you tap into a book, you can see the same stats along with the number of &#8220;editions&#8221; I currently have along with the option to add another (so I can track an iPhone and iPad version of the same book). The final screen is the data for the current edition which requires a position, introduction, and length; this is all agnostic as I mentioned earlier in that it isn&#8217;t necessarily a &#8220;page&#8221; as those can change in the digital world (although Kindle does have a static id known as a <a href="https://www.techwalla.com/articles/what-does-location-mean-on-a-kindle">Location</a>).</p><p>The formula for working out the percentage completed is very simple:</p><p><em>progress = (position - introduction) / (length - introduction)</em></p><p>Calculating a rough &#8220;time remaining&#8221; is also fairly simple:</p><p><em>(1 - progress) / (progress / totalHoursSoFar)</em></p><p>With this I end up getting a fairly accurate idea of how long it will take me to finish a book. Crucially, this is also accurate after just one logged session as the introduction of a book is taken into account. With other apps, I&#8217;ve found that a 30 page foreword or front loaded illustrations and maps can drastically skew reading time with it only becoming more accurate the further you get through the book.</p><p>These calculations also work well if the <em>length </em>parameter changes. For example, if I change the font size on my iPad then I just update the length and everything will still work as I&#8217;m not calculating the change between reading sessions but instead logging the duration I read for along with my current position as a percentage.</p><p>The final thing I added was support for audiobooks:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0jjW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fede94cf3-4831-43fd-a8e0-5b3ccc509171_1339x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0jjW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fede94cf3-4831-43fd-a8e0-5b3ccc509171_1339x2716.png 424w, https://substackcdn.com/image/fetch/$s_!0jjW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fede94cf3-4831-43fd-a8e0-5b3ccc509171_1339x2716.png 848w, https://substackcdn.com/image/fetch/$s_!0jjW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fede94cf3-4831-43fd-a8e0-5b3ccc509171_1339x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!0jjW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fede94cf3-4831-43fd-a8e0-5b3ccc509171_1339x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0jjW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fede94cf3-4831-43fd-a8e0-5b3ccc509171_1339x2716.png" width="286" height="580.1165048543689" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/ede94cf3-4831-43fd-a8e0-5b3ccc509171_1339x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2716,&quot;width&quot;:1339,&quot;resizeWidth&quot;:286,&quot;bytes&quot;:389522,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0jjW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fede94cf3-4831-43fd-a8e0-5b3ccc509171_1339x2716.png 424w, https://substackcdn.com/image/fetch/$s_!0jjW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fede94cf3-4831-43fd-a8e0-5b3ccc509171_1339x2716.png 848w, https://substackcdn.com/image/fetch/$s_!0jjW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fede94cf3-4831-43fd-a8e0-5b3ccc509171_1339x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!0jjW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fede94cf3-4831-43fd-a8e0-5b3ccc509171_1339x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The difference here is that I only want to log how much time is remaining; there is no need for a duration as it can be determined automatically and I don&#8217;t need to worry about the intro as there are no &#8220;how long to finish&#8221; calculations. However, it&#8217;s all stored in the same way; the UI is asking for time remaining but it will just subtract that from the length to determine a position in seconds and store that.</p><p>Oh, I also updated the app icon:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!P3jq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6ad59da-a15f-443d-b32e-154732e3f30d_1024x500.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!P3jq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6ad59da-a15f-443d-b32e-154732e3f30d_1024x500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!P3jq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6ad59da-a15f-443d-b32e-154732e3f30d_1024x500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!P3jq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6ad59da-a15f-443d-b32e-154732e3f30d_1024x500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!P3jq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6ad59da-a15f-443d-b32e-154732e3f30d_1024x500.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!P3jq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6ad59da-a15f-443d-b32e-154732e3f30d_1024x500.jpeg" width="384" height="187.5" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/d6ad59da-a15f-443d-b32e-154732e3f30d_1024x500.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:500,&quot;width&quot;:1024,&quot;resizeWidth&quot;:384,&quot;bytes&quot;:52983,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!P3jq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6ad59da-a15f-443d-b32e-154732e3f30d_1024x500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!P3jq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6ad59da-a15f-443d-b32e-154732e3f30d_1024x500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!P3jq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6ad59da-a15f-443d-b32e-154732e3f30d_1024x500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!P3jq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6ad59da-a15f-443d-b32e-154732e3f30d_1024x500.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The previous icon when the app was <em>Time Well Spent</em> was based on the <a href="https://en.wikipedia.org/wiki/Swiss_railway_clock">Swiss Railway Clock</a> but for <em>Bookmark</em> I decided to show a red bookmark over the top of some text from the opening chapter of one of my favourite books, <em><a href="https://www.gutenberg.org/ebooks/1232">The Prince </a></em><a href="https://www.gutenberg.org/ebooks/1232">by Niccol&#242; Machiavelli</a>.</p><p>I highly doubt I&#8217;ll do anything else with this app as it is built purely to deal with my idiosyncrasies with time and reading on multiple platforms. For most people <a href="https://booktrack.app">Book Tracker</a> or <a href="https://getbookly.com">Bookly</a> are fine choices but if you&#8217;re reading this and thinking &#8220;I want this app&#8221; then let me know!</p><h2>Back Seat Shuffle</h2><p>If I&#8217;m going on a long drive with my two young children, I&#8217;ll load up an iPad with some videos and stick it in a pouch on the back of a seat to keep them entertained. Initially this started as a few films and a couple of their TV series on a USB-C stick but I&#8217;ve gradually started putting a few shows directly onto the iPad so they can be played via VLC. Why? Well, when using an external drive you&#8217;re limited to using the Files app which uses Quick View for video playback; this is fine for a film but for TV you have to go and start a new episode after the previous one finishes (and that involves my wife precariously leaning into the back without a seatbelt which isn&#8217;t ideal). I moved to using VLC for TV shows as they then play sequentially avoiding that problem but it can&#8217;t play from an external drive so I have to put things directly onto the limited storage of the device.</p><p>For a couple of weeks I&#8217;ve been toying with the idea of whether I could build a better app, one that would let me:</p><ol><li><p>Plug in an external drive</p></li><li><p>Show each series with a nice image</p></li><li><p>Play episodes randomly without needing to copy the video to the device</p></li></ol><p>After a 3 hour drive to visit my mother, the priority for this has now increased exponentially &#128514;</p><p>To begin with, I needed to know if it is even possible to view external files within an app on iOS. It is, and has been since the introduction of <code>UIDocumentPickerViewController</code> in iOS 13 however the <a href="https://developer.apple.com/documentation/uikit/uidocumentpickerviewcontroller">documentation</a> left me a little confused:</p><blockquote><p>Both the open and export operations grant access to documents outside your app&#8217;s sandbox. This access gives users an unprecedented amount of flexibility when working with their documents. However, it also adds a layer of complexity to your file handling. External documents have the following additional requirements:</p><ul><li><p>The open and move operations provide security-scoped URLs for all external documents. Call the <code>startAccessingSecurityScopedResource()</code> method to access or bookmark these documents, and the <code>stopAccessingSecurityScopedResource()</code> method to release them. If you&#8217;re using a <code>UIDocument</code>subclass to manage your document, it automatically manages the security-scoped URL for you.</p></li><li><p>Always use file coordinators (see <code>NSFileCoordinator</code>) to read and write to external documents.</p></li><li><p>Always use a file presenter (see <code>NSFilePresenter</code>) when displaying the contents of an external document.</p></li><li><p>Don&#8217;t save URLs that the open and move operations provide. You can, however, save a bookmark to these URLs after calling <code>startAccessingSecurityScopedResource()</code> to ensure you have access. Call the <code>bookmarkData(options:includingResourceValuesForKeys:relativeTo:)</code> method and pass in the <code>withSecurityScope</code> option, creating a bookmark that contains a security-scoped URL.</p></li></ul></blockquote><p>External files can only be accessed via a security-scoped URL and all of the tutorials I&#8217;d seen online relating to this were demonstrating how you could access a file and then copy it locally before removing that scope. I was therefore unsure how it would work in terms of streaming video (as it would go out of scope and lose security clearance) nor if I&#8217;d be able to retain access after displaying a directory and then wanting to start playback.</p><p>It turns out that it is all possible using a system known as &#8220;bookmarks&#8221;. In practice, a user will be shown their external drive in an OS controlled modal view and can select a folder, the URL of which is returned to my app. I then call the &#8220;start accessing security scoped resource&#8221; and convert that URL to a <a href="https://developer.apple.com/documentation/foundation/nsurl/1417795-bookmarkdata">bookmark</a> which is stored locally on my device and then close the security scoped resource. That bookmark can be used at any point to gain access to the drive (so long as it hasn&#8217;t been disconnected in which case the bookmark tells the app it is &#8220;stale&#8221; and therefore no longer working) and you can then interact with the URL the bookmark provides in the same way as you would with a local file.</p><p>I now have a basic prototype working which lets me select a directory and starts playing a video. I&#8217;ve named it &#8220;Back Seat Shuffle&#8221;.</p><p>All I need to do now is build out the actual interface so it looks a bit nicer and then the autoplay functionality so it will play another random video from the same directory when the current one ends.</p><p>Why have I told you this fairly long-winded and technical story? Well, this is one of those apps that will never see the light of day beyond the one very specific device I use in the car maybe twice a year. However, it demonstrates how you can approach any project:</p><p><em><strong>Idea &gt; Investigate &gt; Prototype &gt; Complete</strong></em></p><p>I see people every day online that think the first part, the idea, is everything. That&#8217;s not true, but neither is it sensible to try and take your idea and jump to the end stage without a) investigating whether it is even feasible or b) testing the core functionality to make sure it works.</p><p>With this app, I was unsure whether APIs would exist to allow this (as it isn&#8217;t a feature in a great many media apps I&#8217;ve tried) so I needed to investigate; that showed that it <em>looked</em> possible but I was still not 100% clear on how it would in practice hence the need for a prototype. The joy of working this way is that you can see incremental progress towards your goal and the work required to get from prototype to finished app is mercifully small!</p><h2>End of year recommendations</h2><p>As we&#8217;ve come to the end of 2022 I thought I&#8217;d do something slightly different this issue and instead pick my favourite video games, board games, TV shows, films, and books from across the year.</p><h4>Video Games</h4><p>I played 94 different games in 2022 for a combined time of 639 hours (~26.5 days)<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>. This was split across Xbox (345 hrs), PC (176 hrs), Steam Deck (48 hrs), iOS (39 hrs), Switch (28 hrs), Playdate (2 hrs), and Quest (0.5 hrs). </p><p>My most played games were:</p><ul><li><p>Elden Ring - 104hrs</p></li><li><p>Farming Simulator 22 - 74hrs</p></li><li><p>Rocket League - 53hrs</p></li><li><p>Stardew Valley - 38hrs</p></li><li><p>Vampire Survivors - 30hrs</p></li></ul><p>My favourite games were&#8230;</p><p><strong><a href="https://www.fellowtraveller.games/citizen-sleeper">#3. Citizen Sleeper</a><br></strong>This was the hardest slot to pick as there were a number of very good games this year, but I think Citizen Sleeper gets it for the way in which it mixed up a well written cyberpunk story with a cool dice mechanic plus the clocks mechanic from <em>Blades in the Dark</em>. It&#8217;s available on Game Pass and has had some free DLC updates already so definitely worth trying.</p><p><strong><a href="https://www.tinykingame.com">#2. Tinykin</a><br></strong>I&#8217;m a big fan of &#8220;collectathon&#8221; games like <em>Banjo-Kazooie</em> so <em>Tinykin</em> was a wonderful surprise when it appeared on Game Pass. It evokes those old collectable games from the &#8216;90s but improves the user experience significantly with a high level of polish. It is also beautiful to look at with it&#8217;s mixture of 2D cartoon characters in a rich &#8220;Honey I Shrunk The Kids&#8221; style world. This would easily have been my #1 if not for&#8230;</p><p><strong><a href="https://en.bandainamcoent.eu/elden-ring/elden-ring">#1. Elden Ring</a></strong><br>I&#8217;ll go ahead and say this is probably my favourite game of all time, something I don&#8217;t say lightly. I had never played a FromSoftware game before and typically do not like &#8220;hard&#8221; fights so it was a surprise to me when a few days before launch I decided I&#8217;d give it a try, and then an even bigger surprise when it sunk it&#8217;s hooks into me and didn&#8217;t let go for over 100 hours. The fights are hard but your ability to go away and level up by exploring mean that you&#8217;re never really stuck. However, it&#8217;s the exploration that got me. This game is huge. Seriously huge. It&#8217;s also detailed. Insanely detailed. I haven&#8217;t experienced the thrill of exploration in this way since <em>Breath of the Wild</em> and this takes that and dials it up significantly. There is so much to see and do yet I know I have not seen anywhere near everything. This game pretty much ruined the rest of the year for me as there was nothing even close to being in it&#8217;s league. Since finishing it I&#8217;ve watched countless hours of lore videos on YouTube, bought 2 huge bound books that detail every aspect of the game, and gone all in on the most expensive pledge for the upcoming <a href="https://www.kickstarter.com/projects/steamforged/elden-ring-the-board-game">board game</a>. This is not just the game of the year, it is the game of the century.</p><h4>Board Games</h4><p>I have only recently started tracking the time I spend on board games so I don&#8217;t have any particular breakdowns this year. Instead, I&#8217;ll share my top 3 games I played in 2022:</p><p><strong><a href="https://boardgamegeek.com/boardgame/280748/escape-dark-sector">#3: Escape the Dark Sector</a><br></strong>A fun little game for 1-4 players that sees you trying to escape a space station in a narrative adventure akin to a Fighting Fantasy book, just with cards and a beefier combat mechanic based on dice rolls. I&#8217;ve only played it a couple of times but I really dig the black and white sci-fi aesthetic and the narrative gameplay.</p><p><strong><a href="https://boardgamegeek.com/boardgame/255984/sleeping-gods">#2: Sleeping Gods</a></strong><br>If <em>Escape the Dark Sector</em> was my favourite bite-sized narrative adventure, <em>Sleeping Gods</em> is my buffet-sized narrative adventure. You crew a ship in the 1920s that has been lost in a strange world and need to find your way home. The game comes with a giant atlas which you sail across and a &#8220;choose your own adventure&#8221; style tome that is filled with quests to complete and choices to make. One of my favourite things is that you unlock keyword cards when you complete certain tasks and these keywords open up extra choices in the narrative; it&#8217;s a very smart way of taking the branching dialogue of something like an Inkle video game and bringing it back to the tabletop.</p><p><strong><a href="https://boardgamegeek.com/boardgame/256680/return-dark-tower">#1: Return to Dark Tower</a></strong><br>I already talked about this game a lot <a href="https://www.thedododeveloper.com/p/2-music-library-tracker-v20">last issue</a> but it was definitely my favourite game this year due to the way it takes an app-driven board game to the next level by letting the app control a physical component on the table. A grown man shouldn&#8217;t squeal with delight when a plastic tower lights up or makes a sound but I do. Every. Single. Time. I like the game so much I made an app to <a href="https://dodoapps.io/return-to-dark-tower-assistant/">improve the speed of play</a>. I&#8217;ve ordered the upcoming Covenant expansion along with a backorder of the previous Alliances expansion so looking forward to hopefully getting some new content to play with later this year!<br></p><h4>TV Shows</h4><p>I watched 33 TV shows for a combined time of 217 hours (~9 days) in 2022. It&#8217;s incredibly hard for me to even pick a top 3 as there were a lot of great shows including <em>Severance</em>, <em>The Lord of the Rings: The Rings of Power</em>, <em>Star Wars: Andor</em>, <em>Only Murders in the Building</em>, and <em>House of the Dragon</em>. Any of those could have been up there as they each had their own special moments (although I will give a shout out to <em>Andor</em> and <em>House of the Dragon</em> as I had such low expectations going in!).</p><p>They all pail in comparison to the final season of <em><strong><a href="https://www.netflix.com/title/80021955">Better Call Saul</a></strong><a href="https://www.netflix.com/title/80021955"> </a></em> though which is my #1 recommendation this year. There simply wasn&#8217;t anything to match the characters, storylines, and incredible art direction<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>. There are a few niggles I have with the ending but even still it towers above everything else on my watched list.</p><h4>Films</h4><p>I usually don&#8217;t watch a large number of films so I kicked the year off with a weekly &#8220;Monday Movie&#8221; with the aim to watch 50 films by the end of the year. I managed 38 but ashamedly 18 of those were Marvel films due to me trying to keep pace with the new podcast <a href="https://podcasts.apple.com/gb/podcast/marvel-calls/id1633502825">Marvel Calls</a> (I&#8217;d only ever seen a handful so it&#8217;s been interesting to go through them and understand more of what is actually happening in <em>Guardians of the Galaxy</em>, for example).</p><p>I only watched four films that were released this year &#8211;&nbsp;<em>Death on the Nile </em>(it was alright), <em>Chip &#8216;n Dale: Rescue Rangers </em>(surprisingly good), <em>The Bob&#8217;s Burgers Movie </em>(better than expected), and <em>Glass Onion </em>(wait for it) - so instead I&#8217;ll just pick the 3 films that I enjoyed the most this year from everything I watched:</p><p><strong><a href="https://www.netflix.com/title/80198478">#3: Calibre</a></strong><br>The story of a pair of friends who go shooting in the Scottish Highlands and end up attempting to cover up an accident. I won&#8217;t say more than that but I think about this film <em>a lot</em>. Excellent acting with a small cast in a slightly creepy village. Highly recommended.</p><p><strong><a href="https://www.netflix.com/title/81458416">#2: Glass Onion</a></strong><br>I told you we&#8217;d get there. The sequel to <em>Knives Out</em> (which I genuinely believed was a fever dream after I watched it on a plane whilst I was ill) which was released on Netflix just last week. Daniel Craig gets to swagger around solving a traditional locked-room mystery set during the pandemic. Top marks for Craig&#8217;s fashion stylings and saying &#8220;shitballs&#8221; in his inimitable Southern drawl.</p><p><strong><a href="https://www.disneyplus.com/en-gb/movies/the-life-aquatic-with-steve-zissou/5mR8WrAw3dVa">#1: The Life Aquatic with Steve Zissou</a></strong><br>Oh how I love me a Wes Anderson film. The colours, the framing, the colours, Jeff Goldblum, did I mention the colours? Beautifully shot with some fine performances from Bill Murray and the aforementioned Goldblum. I mean <a href="https://www.youtube.com/watch?v=vFehGXnuNrg">just watch this</a>. Need I say more?</p><p></p><h4>Books</h4><p>I have had an exceptionally bad year when it comes to reading. I aim to read for 30 minutes per day and finish 20 books per year but I reached a paltry average of 6 minutes per day (~40 hours total) and only finished 9 books. They also weren&#8217;t that good &#129318;&#127995;&#8205;&#9794;&#65039;</p><p>If I had to pick a favourite it would be <em><a href="https://www.goodreads.com/book/show/30646488-the-haunting-of-ashburn-house">The Haunting of Ashburn House</a></em><a href="https://www.goodreads.com/book/show/30646488-the-haunting-of-ashburn-house"> by Darcy Coates</a> as it was fairly standard &#8220;spooky house on the hill&#8221; but in a modern setting. I also really enjoyed <em><a href="https://www.goodreads.com/book/show/46000520-the-thursday-murder-club">The Thursday Murder Club</a></em><a href="https://www.goodreads.com/book/show/46000520-the-thursday-murder-club"> by Richard Osman</a> but everybody does hence it&#8217;s ongoing dominant position at the top of the book charts. However, neither of those came out this year so my only recommendation I can really give is <em><a href="https://books.apple.com/gb/book/severance/id1613220757">Severance: The Lexington Letter</a></em>, a free companion book to the <em>Severance</em> TV show that answers some questions and adds a few more.</p><p>I&#8217;ll do better this year. Promise. &#129310;&#127995;</p><h4></h4><h2>Roadmap</h2><p>The roadmap is my way of committing to what I&#8217;m going to do over the next 2 weeks.</p><h4>15th December - 4th January</h4><ul><li><p>Issue a TestFlight update for <em>Music Library Tracker v2.0 </em> &#9989;</p></li></ul><ul><li><p>Rebuild <em>Game Track </em> &#9989;</p></li><li><p>Tidy up <em>Time Well Spent </em> &#9989;</p></li></ul><p>As I mentioned in the introduction, the <em>Game Track </em>work is done but I was already hitting the &#8220;post too long for email&#8221; message on Substack without going through that so I&#8217;ll save that for the next issue when I will have done a bit more to it. I was very happy to get the <em>Time Well Spent</em> app rebranded to <em>Bookmark</em> and even happier to have started on something small and new in the form of <em>Back Seat Shuffle</em>.</p><h4>5th January - 18th January (Issue #4)</h4><ul><li><p>Keep working on <em>Music Library Tracker v2.0</em> to resolve the issues mentioned above. I also want to improve the UI, especially on iPad.</p></li><li><p>I&#8217;d like to start looking into Swift Charts so will aim to add a stats page to <em>Game Track</em>. I&#8217;m also keen to look at a particular new API in iOS 16&#8230;</p></li><li><p>Whilst I don&#8217;t need <em>Back Seat Shuffle</em> until I go on holiday in March I&#8217;d like to get it finished off as otherwise it&#8217;ll gnaw away at me! I&#8217;ll stick the code up on GitHub.</p></li></ul><p>That wraps it up for this issue. I hope you found something of interest and that you&#8217;ll be able to recommend the newsletter to your friends, family, and colleagues. You can always comment on this issue or by emailing me directly via <a href="mailto:ben@bendodson.com">ben@bendodson.com</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/subscribe?"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/3-yule-logging/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.thedododeveloper.com/p/3-yule-logging/comments"><span>Leave a comment</span></a></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Heroku has a free plan but it puts your server to sleep. This isn&#8217;t a huge issue but it meant that when I opened the app to log something I&#8217;d have to wait 10-20 seconds for it to be available; this was fairly tedious so I was paying $7 a month to avoid that. This was a bit extravagant when I was logging books, tv, and projects but it was utterly ridiculous when it was just books, especially in a year where I read very little.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>If you&#8217;re really interested you can <a href="https://bendodson.s3-eu-west-1.amazonaws.com/dodo-developer/games-2022.csv">download a csv</a> of my 2022 gaming time.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>OK, <em>Severance</em> gets there on art direction. Whilst you&#8217;re down here you can also see everything I watched (tv and films) on my <a href="https://trakt.tv/users/bendodson/year/2022">Trakt year in review</a>.</p></div></div>]]></content:encoded></item><item><title><![CDATA[#2: Music Library Tracker v2.0]]></title><description><![CDATA[Find Spatial Audio in your music library with an exclusive TestFlight beta]]></description><link>https://www.thedododeveloper.com/p/2-music-library-tracker-v20</link><guid isPermaLink="false">https://www.thedododeveloper.com/p/2-music-library-tracker-v20</guid><dc:creator><![CDATA[Ben Dodson]]></dc:creator><pubDate>Wed, 14 Dec 2022 14:45:53 GMT</pubDate><enclosure url="https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/2ee6625b-d784-42f1-917f-2d6d2619891e_1920x1080.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dear Reader,</p><p>As a software developer with many public-facing projects I regularly get requests for new features. I&#8217;m usually pretty good at adding any ideas to my to-do list app and then churning through them the next time I happen to work on that project but sometimes they get stuck there for a little while. This usually happens if the task is complex or if it&#8217;s a feature that I wouldn&#8217;t use myself (and it&#8217;s almost fatal if it&#8217;s a combination of the two). That latter point is particularly important as nearly everything I build starts as being something I want to use myself; I don&#8217;t think I&#8217;ve ever started an app that wasn&#8217;t initially built for me.</p><p>This is a great way to get started in app development as it doesn&#8217;t feel like &#8220;work&#8221; if you&#8217;re building a fun tool for yourself. Once you&#8217;ve got something built, it&#8217;s then much easier to monetise it or tweak it so it can be used by a wider audience. Such is the case with nearly everything in today&#8217;s issue; every project started as a fun distraction to fulfil a need I had but is now something that can be enjoyed by many more people.</p><p>One thing I pride myself on is replying to every email I get (usually within a day or two) but more than that I&#8217;ll always send a follow-up email when a requested feature is added&#8230; even if it was 2052 days ago as you&#8217;ll see shortly &#128514;</p><p>I&#8217;d like to give a big thank you to all of you who sent in feedback regarding the TestFlight beta of <em>Catacombs</em> in <a href="https://www.thedododeveloper.com/p/1-catacombs-spatial-audio-and-animated">the last issue</a>. I&#8217;d also like to thank you for all the kind words of encouragement; those, combined with the roadmap I put together last time, have led to an incredibly productive fortnight!</p><p>I hope you&#8217;ll find something of interest in today&#8217;s issue and I look forward to hearing any feedback you may have.</p><p><strong>&#8212; Ben</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/2-music-library-tracker-v20/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.thedododeveloper.com/p/2-music-library-tracker-v20/comments"><span>Leave a comment</span></a></p><h2>Contents</h2><ul><li><p>Music Library Tracker v2.0 (Spatial Audio Upgrade)</p></li><li><p>Spatial Audio Finder updates</p></li><li><p>Artwork Finder updates</p></li><li><p>New app: Return to Dark Tower Assistant</p></li><li><p>Using a Stream Deck for iOS Development</p></li><li><p>Recommended Links</p></li><li><p>Roadmap</p></li></ul><p></p><h2>Music Library Tracker v2.0</h2><p>As some of you may be aware, I have an app named <em><a href="https://itunes.apple.com/us/app/music-tracker-automatic-tracking/id1062583712?ls=1&amp;mt=8">Music Library Tracker</a></em> that I built back in 2016 to help me keep an eye on the changes Apple was making to my music library. This all came about as iTunes Match was frequently messing with my songs and either converting studio versions to inferior live versions or just deleting songs out of my library. It&#8217;s my most downloaded app and I recently did a big update which sped the scanning up and fixed a number of UI issues.</p><p>The next big update is to include Spatial Audio matching as I detailed in the <a href="https://www.thedododeveloper.com/i/86130846/spatial-audio-matching">last issue</a>:</p><blockquote><p>The dream scenario is that you can open the app, press a button, and within a few seconds have a new playlist generated for you which contains all of your Spatial Audio tracks.</p></blockquote><p>The dream is very much alive as I have managed to complete a test version of the update which is ready for you today:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://testflight.apple.com/join/uKS7gUcm&quot;,&quot;text&quot;:&quot;Join TestFlight beta&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://testflight.apple.com/join/uKS7gUcm"><span>Join TestFlight beta</span></a></p><p><em>* Music Library Tracker v2.0 will run on any iOS device running iOS 13 or greater. If you already have Music Library Tracker installed then this will update that version; you won&#8217;t be able to downgrade. This beta will expire when the app update is publicly released in January.</em></p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!APEo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7d7fbdf-7911-4243-a868-dae932e76dce_4470x2936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!APEo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7d7fbdf-7911-4243-a868-dae932e76dce_4470x2936.png 424w, https://substackcdn.com/image/fetch/$s_!APEo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7d7fbdf-7911-4243-a868-dae932e76dce_4470x2936.png 848w, https://substackcdn.com/image/fetch/$s_!APEo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7d7fbdf-7911-4243-a868-dae932e76dce_4470x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!APEo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7d7fbdf-7911-4243-a868-dae932e76dce_4470x2936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!APEo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7d7fbdf-7911-4243-a868-dae932e76dce_4470x2936.png" width="1456" height="956" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/a7d7fbdf-7911-4243-a868-dae932e76dce_4470x2936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:956,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:6057489,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!APEo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7d7fbdf-7911-4243-a868-dae932e76dce_4470x2936.png 424w, https://substackcdn.com/image/fetch/$s_!APEo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7d7fbdf-7911-4243-a868-dae932e76dce_4470x2936.png 848w, https://substackcdn.com/image/fetch/$s_!APEo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7d7fbdf-7911-4243-a868-dae932e76dce_4470x2936.png 1272w, https://substackcdn.com/image/fetch/$s_!APEo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7d7fbdf-7911-4243-a868-dae932e76dce_4470x2936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The new Spatial Audio tab, the fast matching process, and a generated playlist within Apple Music</figcaption></figure></div><p>To begin with I&#8217;ve redesigned the app to use a tab-bar system:</p><ul><li><p>Activity - the original features of the app including a date-based overview of changes to your music library</p></li><li><p>Spatial Audio - the new functionality which lists all of the upgraded tracks broken down by date</p></li><li><p>Settings - this used to be in a modal view accessible from the Activity page but I always think two tabs looks odd; three looks much better.</p></li></ul><p>In terms of the Spatial Audio update, when you first appear on the page there will be no tracks available as your library has not yet been scanned. If you press the small refresh icon in the top-right hand corner, a modal view will appear as the scan takes place. The first half of the process will send all of your track identifiers (in batches of 5000) to my server and get back a response for each one that will be -1 (not in the database), 0 (in the database but not updated), or 1 (it&#8217;s a Spatial Audio track). This information is then stored on your device and any Spatial Audio tracks are added to a new playlist named &#8220;Spatial Audio BETA&#8221;<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>. On my library of ~7k tracks this takes less than 1 second.</p><p>If any of your tracks are not yet in my database, these are then synced up to the server (again in batches of 5000 and again being done in less than a second) and you&#8217;ll get an alert warning you that there are <em>n</em> number of unknown tracks and that you should do another scan later; the reason for this is that the tracks will enter a queue to be checked which may take anywhere from a few seconds to a few hours depending on how many other testers are uploading their libraries at the same time.</p><p>I was <a href="https://twitter.com/bendodson/status/1602765863563956224">incredibly pleased with the performance</a> when testing this update with my own music library; it vastly exceeded what I anticipated, so much so that I may well be able to add it as an automated daily check. For now though you&#8217;ll need to go in and press that refresh button whenever you want to scan for updated tracks. I should also mention that the Apple Music playlist is always created but in the final version it&#8217;ll be something you can disable.</p><p>I would greatly appreciate any feedback you may have, especially if you run into any problems with the Spatial Audio matching. You can always comment on this post, provide feedback directly through TestFlight, or email me via <a href="mailto:ben@bendodson.com">ben@bendodson.com</a>.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/2-music-library-tracker-v20/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.thedododeveloper.com/p/2-music-library-tracker-v20/comments"><span>Leave a comment</span></a></p><p><em>Also, feel free to publicly share any screenshots, videos, or thoughts on the app update (although I&#8217;d obviously prefer you share any issues you encounter with me first!)</em></p><h2>Spatial Audio Finder / @NewSpatialAudio</h2><p>Before working on the <em>Music Library Tracker</em> update, I had a number of changes I had to make to the infrastructure that was powering the Spatial Audio matching (I won&#8217;t go through how the matching works as I detailed that <a href="https://www.thedododeveloper.com/i/86130846/spatial-audio-matching">last issue</a>).</p><ol><li><p>Add new columns for a unique hash, artist identifier, and a list of all audio variants. This was partially for future proofing and partially for a new feature which I&#8217;ll come onto shortly.</p></li><li><p>Recrawl the entire database of ~1.3 million tracks in order to add that new information.</p></li><li><p>Improve some of the SQL queries that were slowing down the process (mostly due to sorting by dates in order to try and crawl any new tracks in date order; I&#8217;ve abandoned this for the sake of speed so it&#8217;ll just pick a random track that hasn&#8217;t been crawled yet)</p></li><li><p>Update the Amazon RDS instance that hosts the database. It was using an older and slower instance type so should run a lot faster now. I also bumped up the storage and set it to auto-expand as it conveniently ran out of space 2 days ago (luckily it didn&#8217;t happen just after this issue went out &#128517;)</p></li></ol><p>With these infrastructure changes in place I was then able to add a number of new features to the <a href="https://bendodson.com/projects/spatial-audio-finder/">Spatial Audio Finder</a> and to the <a href="https://twitter.com/NewSpatialAudio">@NewSpatialAudio</a> twitter account starting with artist searching. When I first created the finder, I was just doing a basic text search against the artist; for example if you typed &#8220;Fall Out Boy&#8221; it would go and retrieve anything from the database that matched that artist name. I was sensible enough to make it case insensitive but it had two problems; firstly, the name doesn&#8217;t always match exactly as you think it should i.e. you might type &#8220;panic at the disco&#8221; but that won&#8217;t match anything as in the database it&#8217;s stored as &#8220;Panic! At the Disco&#8221;. Secondly, nobody read the instructions that said it searched by artist name so they would frequently try and search for song titles, album names, or Apple Music URLs. To fix this, I now take the string that is entered and use that to do an artist search against the Apple Music API and then return a list of artists; the user can then click on that which will use the artist ID to match against the database. This is far more reliable and also means those edge cases with artist name matching are handled by Apple</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RnUs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fef4dd68c-1aa8-4a8e-9534-d06c838b9d69_1658x486.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RnUs!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fef4dd68c-1aa8-4a8e-9534-d06c838b9d69_1658x486.png 424w, https://substackcdn.com/image/fetch/$s_!RnUs!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fef4dd68c-1aa8-4a8e-9534-d06c838b9d69_1658x486.png 848w, https://substackcdn.com/image/fetch/$s_!RnUs!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fef4dd68c-1aa8-4a8e-9534-d06c838b9d69_1658x486.png 1272w, https://substackcdn.com/image/fetch/$s_!RnUs!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fef4dd68c-1aa8-4a8e-9534-d06c838b9d69_1658x486.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RnUs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fef4dd68c-1aa8-4a8e-9534-d06c838b9d69_1658x486.png" width="1456" height="427" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/ef4dd68c-1aa8-4a8e-9534-d06c838b9d69_1658x486.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:427,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:69138,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!RnUs!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fef4dd68c-1aa8-4a8e-9534-d06c838b9d69_1658x486.png 424w, https://substackcdn.com/image/fetch/$s_!RnUs!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fef4dd68c-1aa8-4a8e-9534-d06c838b9d69_1658x486.png 848w, https://substackcdn.com/image/fetch/$s_!RnUs!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fef4dd68c-1aa8-4a8e-9534-d06c838b9d69_1658x486.png 1272w, https://substackcdn.com/image/fetch/$s_!RnUs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fef4dd68c-1aa8-4a8e-9534-d06c838b9d69_1658x486.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Another benefit of this process is that I can capture artist searches more accurately. For example, a user messaged me to say that the Spatial Audio Finder wasn&#8217;t working as it wasn&#8217;t showing the latest album for The Black Eyed Peas. The reason for this is that I had some of their older albums in my library so when I did the initial matching process it added those to the database. Previously, when somebody searched for an artist, I would do a quick lookup to find any tracks by that user but only if they weren&#8217;t in the database already (and in this case they were there, just not all of their music). Now I store the artist id when somebody searches and do a full crawl of their discography if it hasn&#8217;t been done before. This change alone has caused the database to double in size to nearly 3 million tracks in the past week - turns out the Spatial Audio Finder is being used a lot!</p><p>The final thing I wanted to improve was the <a href="https://twitter.com/NewSpatialAudio">@NewSpatialAudio</a> Twitter account as there were two things that bothered me; sometimes it would tweet the same songs if they were featured on multiple albums and it would also send a tweet for every track that changed if the entire album hadn&#8217;t been upgraded. In the first instance, I decoupled the URL from the message and then kept a historical backlog so I could prevent it from sending the same text with a different album link. Secondly, I started batching together updates and created my own website to show how much of an album had been updated; in this way, if 12 of 16 tracks on a deluxe album are in Spatial Audio, you just get one tweet to the new webpage rather than 12 separate ones for each track:</p><div class="twitter-embed" data-attrs="{&quot;url&quot;:&quot;https://twitter.com/NewSpatialAudio/status/1601256853270368259&quot;,&quot;full_text&quot;:&quot;11 tracks upgraded to Spatial Audio in Spaceman (Deluxe) by Nick Jonas\n\n&quot;,&quot;username&quot;:&quot;NewSpatialAudio&quot;,&quot;name&quot;:&quot;Spatial Audio Releases&quot;,&quot;profile_image_url&quot;:&quot;&quot;,&quot;date&quot;:&quot;Fri Dec 09 16:45:58 +0000 2022&quot;,&quot;photos&quot;:[],&quot;quoted_tweet&quot;:{},&quot;reply_count&quot;:0,&quot;retweet_count&quot;:0,&quot;like_count&quot;:1,&quot;impression_count&quot;:0,&quot;expanded_url&quot;:{&quot;url&quot;:&quot;https://spatialaudio.dodoapps.io/albums/1558244186/&quot;,&quot;image&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/a391ade4-11e6-4b79-a2e6-dc8ac8c84596_1200x630.jpeg&quot;,&quot;title&quot;:&quot;Spaceman (Deluxe) by Nick Jonas&quot;,&quot;description&quot;:&quot;11 of 16 tracks have been upgraded to Spatial Audio&quot;,&quot;domain&quot;:&quot;spatialaudio.dodoapps.io&quot;},&quot;video_url&quot;:null,&quot;belowTheFold&quot;:true}" data-component-name="Twitter2ToDOM"></div><p>I was able to work out the correct artwork URL in order to get a nice 16:9 ratio image for each album and then use that with the OpenGraph and Twitter Cards metatags so that it looks a lot nicer in the timeline (complete with a line telling you how much of the album has been upgraded).</p><p>OK, I think that&#8217;s enough Spatial Audio for one issue!</p><p></p><h2>Artwork Finder Updates</h2><p>My original artwork finder, <a href="https://bendodson.com/projects/itunes-artwork-finder/">iTunes Artwork Finder</a>, was created back in <a href="https://bendodson.com/weblog/2011/04/13/itunes-tv-artwork-script/">2011</a> to help me get higher resolution images for my Apple TV. Since then, I&#8217;ve received lots of emails with suggestions for improvements including one last week from subscriber Ruben:</p><blockquote><p>It would be handy if the search field is already focussed and it would be great if the drop down menu for the type of media could be remembered from the last use (set a cookie?)</p></blockquote><p>Adding the auto-focus for the search field was a straightforward addition and having the site remember your previously used settings (i.e. <em>Album</em> and <em>United Kingdom</em>) is something I&#8217;d been meaning to add for a long time. In fact, someone had requested that feature over 5 years ago where it had sat dormant in my to-do app &#128556;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3EYL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6ff0128-a508-44f5-aef6-11b7fa4274b3_816x1146.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3EYL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6ff0128-a508-44f5-aef6-11b7fa4274b3_816x1146.png 424w, https://substackcdn.com/image/fetch/$s_!3EYL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6ff0128-a508-44f5-aef6-11b7fa4274b3_816x1146.png 848w, https://substackcdn.com/image/fetch/$s_!3EYL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6ff0128-a508-44f5-aef6-11b7fa4274b3_816x1146.png 1272w, https://substackcdn.com/image/fetch/$s_!3EYL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6ff0128-a508-44f5-aef6-11b7fa4274b3_816x1146.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3EYL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6ff0128-a508-44f5-aef6-11b7fa4274b3_816x1146.png" width="302" height="424.13235294117646" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/e6ff0128-a508-44f5-aef6-11b7fa4274b3_816x1146.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1146,&quot;width&quot;:816,&quot;resizeWidth&quot;:302,&quot;bytes&quot;:253089,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!3EYL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6ff0128-a508-44f5-aef6-11b7fa4274b3_816x1146.png 424w, https://substackcdn.com/image/fetch/$s_!3EYL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6ff0128-a508-44f5-aef6-11b7fa4274b3_816x1146.png 848w, https://substackcdn.com/image/fetch/$s_!3EYL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6ff0128-a508-44f5-aef6-11b7fa4274b3_816x1146.png 1272w, https://substackcdn.com/image/fetch/$s_!3EYL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6ff0128-a508-44f5-aef6-11b7fa4274b3_816x1146.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The reason I&#8217;d never got around to it was that I was somewhat hesitant to do anything with cookies. I&#8217;ve had them blocked in my browser for the better part of a decade and with directives like the <a href="https://www.cookielaw.org/the-cookie-law/">EU Cookie Law</a> mandating that you need to give users a way to opt-out it just seemed like too much hassle. Another month would roll by and I&#8217;d leave it in the &#8220;for consideration&#8221; pile.</p><p>It turns out it was a 5 minute job to get it working and I didn&#8217;t need to dabble with cookies at all. A cookie is designed specifically to share information that you store on a website with your server as every subsequent web request will send that information with it; that&#8217;s why browsers like Safari will automatically delete cookies after 7 days so users can&#8217;t be tracked endlessly. However, I was able to use <a href="https://www.w3schools.com/html/html5_webstorage.asp">HTML Web Storage</a> for this feature which effectively allows you to store some information against a page for as long as you want (and that page can fetch that information back when it needs it).</p><p>All of my artwork finders have now been updated with both of these features so whether you are searching for artwork from <a href="https://bendodson.com/projects/itunes-artwork-finder/">iTunes</a>, <a href="https://bendodson.com/projects/apple-music-artwork-finder/">Apple Music</a>, or <a href="https://bendodson.com/projects/apple-tv-movies-artwork-finder/">Apple TV</a> your settings will now be preserved between sessions.</p><p>Thanks for the suggestion Ruben (and also Ayaz who beat you to it by 2052 days &#128514;).</p><div><hr></div><p>There is another new feature for the <a href="https://bendodson.com/projects/apple-music-artwork-finder/">Apple Music Artwork Finder</a> this week in the form of support for compilation albums. These are often large box sets such as <a href="https://music.apple.com/us/album/the-u-s-albums/1468023095">The U.S. Albums by The Beatles</a> or <a href="https://music.apple.com/us/album/five-years-1969-1973/1016484169">Five Years 1969-1973 by David Bowie</a> that have an overarching piece of artwork for the whole set but individual artwork for each disc. This artwork is usually only visible in the Apple Music client as you play each track and so I&#8217;d often hypothesised that it was embedded directly into the music files.</p><p>It turns out I was completely missing the obvious solution in front of me. When I fetch album artwork I make some requests using the Apple Music API and adjust the artwork urls provided for each album. If I&#8217;d dug a bit deeper I would have noticed that each track in the album also has an artwork url which 99% of the time will be the same as the album one; for the compilation albums though these urls are different for each disc.</p><p>To add support to my artwork finder I simply loop through each track and fetch out the artwork url. I then deduplicate this and remove the url for the album as a whole; if there is anything left then they are extra pieces of artwork which are then displayed on my webpage. It may well be that there are other use cases for this beyond compilation albums such as extra tracks on a deluxe album. If you spot any, <a href="mailto:ben@bendodson.com">let me know</a>!</p><p>Thanks to Louis for requesting this feature last month (and to Ngyuen and John who requested it 361 and 638 days ago respectively).</p><p></p><h2>New App: Return to Dark Tower Assistant</h2><p><em><a href="https://restorationgames.com/return-to-dark-tower/">Return to Dark Tower</a></em> is a really cool app-driven board game that comes with a physical tower that you connect via Bluetooth to an iPad. The tower lights up, makes sounds, and spins internally to shoot little skulls you place into it over the outlying map. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LEmp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8dd86b60-0746-440c-a4a6-ba68ef7d5975_4032x3024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LEmp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8dd86b60-0746-440c-a4a6-ba68ef7d5975_4032x3024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!LEmp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8dd86b60-0746-440c-a4a6-ba68ef7d5975_4032x3024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!LEmp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8dd86b60-0746-440c-a4a6-ba68ef7d5975_4032x3024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!LEmp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8dd86b60-0746-440c-a4a6-ba68ef7d5975_4032x3024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LEmp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8dd86b60-0746-440c-a4a6-ba68ef7d5975_4032x3024.jpeg" width="1456" height="1092" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/8dd86b60-0746-440c-a4a6-ba68ef7d5975_4032x3024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1092,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2783836,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LEmp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8dd86b60-0746-440c-a4a6-ba68ef7d5975_4032x3024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!LEmp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8dd86b60-0746-440c-a4a6-ba68ef7d5975_4032x3024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!LEmp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8dd86b60-0746-440c-a4a6-ba68ef7d5975_4032x3024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!LEmp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8dd86b60-0746-440c-a4a6-ba68ef7d5975_4032x3024.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>As much as I love it, there are a ton of cards in the game so you can easily forget what abilities you have available to you or miss crucial triggers at key phases in the game. You can also take most actions in any order so it&#8217;s easy to forget what you&#8217;ve done in the current turn, especially if you&#8217;re playing solo. To that end, I built myself a very niche app to keep track of all of the cards I had and all of the moves I&#8217;d made. It&#8217;s called <em>Return to Dark Tower Assistant</em> and is optimised for use in an iPad Slide Over panel so you can use it on top of the app you use to run the game:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Uav2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F862b5d17-7e35-481f-b25b-3792290000fa_2973x2288.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Uav2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F862b5d17-7e35-481f-b25b-3792290000fa_2973x2288.png 424w, https://substackcdn.com/image/fetch/$s_!Uav2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F862b5d17-7e35-481f-b25b-3792290000fa_2973x2288.png 848w, https://substackcdn.com/image/fetch/$s_!Uav2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F862b5d17-7e35-481f-b25b-3792290000fa_2973x2288.png 1272w, https://substackcdn.com/image/fetch/$s_!Uav2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F862b5d17-7e35-481f-b25b-3792290000fa_2973x2288.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Uav2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F862b5d17-7e35-481f-b25b-3792290000fa_2973x2288.png" width="1456" height="1121" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/862b5d17-7e35-481f-b25b-3792290000fa_2973x2288.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1121,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2390211,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Uav2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F862b5d17-7e35-481f-b25b-3792290000fa_2973x2288.png 424w, https://substackcdn.com/image/fetch/$s_!Uav2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F862b5d17-7e35-481f-b25b-3792290000fa_2973x2288.png 848w, https://substackcdn.com/image/fetch/$s_!Uav2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F862b5d17-7e35-481f-b25b-3792290000fa_2973x2288.png 1272w, https://substackcdn.com/image/fetch/$s_!Uav2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F862b5d17-7e35-481f-b25b-3792290000fa_2973x2288.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>There&#8217;s a very slim chance that any of you reading this have a copy of this board game and even if you do it&#8217;s likely a small minority that would find utility in this app. That said, I think it demonstrates what I said in my intro about making apps for yourself; I knew that this was just for me and I&#8217;m publishing it on the off-chance that somebody finds it as useful as I do. I spent far too much time matching the colours to the player boards, getting the fonts just right, and doing things like perfectly embedding the right icons for warrior and spirit tokens. It may only get single-digit downloads, but it&#8217;s an app I&#8217;m proud of.</p><p>Available now on the <a href="https://apps.apple.com/us/app/return-to-dark-tower-assistant/id1659406816">App Store</a>. You can also read more about it on my <a href="https://dodoapps.io/return-to-dark-tower-assistant/">Dodo Apps website</a>.</p><p></p><h2>Using a Stream Deck for iOS Development</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hIhW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed2759a-3d23-4c79-9e61-6d91390006b0_1600x900.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hIhW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed2759a-3d23-4c79-9e61-6d91390006b0_1600x900.jpeg 424w, https://substackcdn.com/image/fetch/$s_!hIhW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed2759a-3d23-4c79-9e61-6d91390006b0_1600x900.jpeg 848w, https://substackcdn.com/image/fetch/$s_!hIhW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed2759a-3d23-4c79-9e61-6d91390006b0_1600x900.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!hIhW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed2759a-3d23-4c79-9e61-6d91390006b0_1600x900.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hIhW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed2759a-3d23-4c79-9e61-6d91390006b0_1600x900.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/9ed2759a-3d23-4c79-9e61-6d91390006b0_1600x900.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:250176,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hIhW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed2759a-3d23-4c79-9e61-6d91390006b0_1600x900.jpeg 424w, https://substackcdn.com/image/fetch/$s_!hIhW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed2759a-3d23-4c79-9e61-6d91390006b0_1600x900.jpeg 848w, https://substackcdn.com/image/fetch/$s_!hIhW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed2759a-3d23-4c79-9e61-6d91390006b0_1600x900.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!hIhW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed2759a-3d23-4c79-9e61-6d91390006b0_1600x900.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I wrote <a href="https://bendodson.com/weblog/2022/12/08/stream-deck-ios-development/">an article</a> about how I use my Stream Deck to help me save time when working on multiple iOS projects per day:</p><blockquote><p>The process typically takes about 20 minutes to run but would take longer if I were doing it manually as there are multiple points that would require user interaction. That I can press one button and have this run seamlessly in the background is of huge benefit to me, especially if I&#8217;m doing multiple builds in a single day.</p></blockquote><p>You can <a href="https://bendodson.com/weblog/2022/12/08/stream-deck-ios-development/">read the full article over at BenDodson.com</a> &#128064;</p><p></p><h2>Recommended Links</h2><h4>Video Games</h4><ul><li><p>As soon as I&#8217;ve finished typing up this email I&#8217;m going to turn on my Xbox and play <a href="https://www.xbox.com/en-GB/games/high-on-life">High on Life</a> which launched this week. I&#8217;ve no idea if it&#8217;s good yet, but I&#8217;ve been looking forward to it for a few months. It&#8217;s developed (and partly voiced) by the guy behind Rick &amp; Morty so if you like that show you&#8217;ll probably want to give this a go. It&#8217;s available on Game Pass.</p></li></ul><h4>Board Games</h4><ul><li><p>My copy of <a href="https://mythicgames.net/game/darkest-dungeon/">Darkest Dungeon</a> finally arrived&#8230; it&#8217;s the <a href="https://twitter.com/bendodson/status/1602607889633497088">biggest board game</a> I&#8217;ve ever owned &#128514; Mythic Games ran a terrible Kickstarter for it but I think it&#8217;s going to be worth the wait given some of the gameplay videos I&#8217;ve watched. I can&#8217;t wait to get stuck in over the Christmas period.</p></li></ul><h4>Podcasts</h4><ul><li><p><a href="https://www.standardedish.com">Standard Edish</a> - I made a promise to myself that I wouldn&#8217;t start a new run of Elden Ring if they announced some DLC for it at The Game Awards. They didn&#8217;t, so I&#8217;m gearing up to start again soon! In the meantime I&#8217;ve been scratching the itch by listening to this lovely podcast in the style of a book club as four friends journey through the game.</p></li></ul><h4>TV Shows</h4><ul><li><p><a href="https://www.disneyplus.com/en-gb/series/star-wars-andor/3xsQKWG00GL5">Andor</a> - Yes it&#8217;s as good as everyone says. Finally a Star Wars TV show that isn&#8217;t set on Tatooine; it isn&#8217;t even mentioned! It also doesn&#8217;t include any Jedi, lightsabers, or R2 units. An excellent thriller with a mostly British cast. Highly recommended.</p></li><li><p><a href="https://www.netflix.com/title/81231974">Wednesday</a> - I&#8217;m a huge Addam&#8217;s Family fan so was very excited when it was announced that Tim Burton was going to be directing a TV series. It&#8217;s essentially Harry Potter meets The Addam&#8217;s Family. It doesn&#8217;t come close to the two films but it&#8217;s still worth a watch if only for Jenna Ortega&#8217;s and Christina Ricci&#8217;s excellent performances.</p></li></ul><h2>Roadmap</h2><p>The roadmap is my way of committing to what I&#8217;m going to do over the next 2 weeks.</p><h4>1st December - 14th December</h4><ul><li><p>Produce a TestFlight beta of <em>Music Library Tracker v2.0</em> with Spatial Audio matching &#9989;</p></li></ul><ul><li><p>Submit <em>Return to Dark Tower Assistant</em> to the App Store &#9989;</p></li><li><p>Write an article about using my Stream Deck for iOS Development &#9989;</p></li><li><p>Revisit the Swift port of Ink I&#8217;ve been working on for the past year &#10060;</p></li></ul><p>Not too bad! I completed the three most important tasks and only failed to get back into Ink as the <em>Spatial Audio Finder</em> required a lot more tinkering than I&#8217;d planned.</p><p>With the Christmas break coming up I expect to do a lot less for the next issue but I still have a few items I want to get through:</p><h4>15th December - 4th January (Issue #3)</h4><ul><li><p>Issue an update on TestFlight to resolve any issues with the <em>Music Library Tracker</em> in anticipation of releasing it publicly in January</p></li><li><p>I built an app for myself and a friend many years ago to track our video game times. This has worked well but I recently started rebuilding it in SwiftUI using the <a href="https://www.igdb.com/api">IGDB database</a>. It&#8217;s not far off completion so that seems like a good pre-Christmas project!</p></li><li><p>I also have an app I built for tracking the books I read (yes, I like tracking things). I built it when SwiftUI first came out and foolishly built an API for it using Vapor as I wanted to try out Swift on the server. The app needs some tidying up and I also want to replace the API.</p></li></ul><p>That wraps it up for this issue. I hope you found something of interest and that you&#8217;ll be able to recommend the newsletter to your friends, family, and colleagues. You can always comment on this issue or by emailing me directly via <a href="mailto:ben@bendodson.com">ben@bendodson.com</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.thedododeveloper.com/subscribe?"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/2-music-library-tracker-v20/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.thedododeveloper.com/p/2-music-library-tracker-v20/comments"><span>Leave a comment</span></a></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Annoyingly the playlist will show that it has been created by &#8220;MusicLog&#8221;. This is the internal name for the app and so far there doesn&#8217;t seem to be a way to rename that. I&#8217;m going to spend a bit more time looking into that as I&#8217;d much prefer it to say &#8220;Music Library Tracker&#8221;.</p></div></div>]]></content:encoded></item><item><title><![CDATA[#1: Catacombs, Spatial Audio, and Animated Album Artwork]]></title><description><![CDATA[Prototyping a new dice mechanic for an adventure game and talking about Spatial Audio / animated artwork on Apple Music.]]></description><link>https://www.thedododeveloper.com/p/1-catacombs-spatial-audio-and-animated</link><guid isPermaLink="false">https://www.thedododeveloper.com/p/1-catacombs-spatial-audio-and-animated</guid><dc:creator><![CDATA[Ben Dodson]]></dc:creator><pubDate>Wed, 30 Nov 2022 14:45:57 GMT</pubDate><enclosure url="https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/462cf9f9-47d2-4471-a94f-0801ed4f33d5_1920x1080.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dear Reader,</p><p>Welcome to the first proper issue of <em>The Dodo Developer</em>, a newsletter where I&#8217;ll hopefully be able to give you some insight around independent app development. I&#8217;m expecting that the first few issues will be quite long as I have plenty of apps to go through; I also expect that the format will change quite a bit based on your feedback. You can leave any comments on this issue or you can contact me directly via <a href="mailto:ben@bendodson.com">ben@bendodson.com</a> - I&#8217;m always grateful for your critical feedback.</p><p>In this first issue I&#8217;m going to be giving you early access to a game mechanic I&#8217;ve been working on, give you an update on where I am with Spatial Audio matching, and demonstrate how I was able to get animated album artwork downloads working (and how I fixed them when they broke). I&#8217;ll also be putting a roadmap at the end showing what I plan to work on over the next two weeks as one of the main reasons for doing this was to keep me motivated on my own projects.</p><p>When I launched this newsletter last week I had a rough number of subscribers I hoped to achieve before the end of the year; I&#8217;m very happy (and slightly nervous!) to say that more than 3x that number are already reading these words. Thank you for being one of them. I hope you&#8217;ll find something interesting in today&#8217;s issue.</p><p><strong>&#8212; Ben</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/1-catacombs-spatial-audio-and-animated/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.thedododeveloper.com/p/1-catacombs-spatial-audio-and-animated/comments"><span>Leave a comment</span></a></p><p></p><h2>Contents</h2><ul><li><p><a href="https://www.thedododeveloper.com/i/86130846/catacombs">Catacombs</a>, a dice-based game prototype written in SwiftUI</p></li><li><p>An update on <a href="https://www.thedododeveloper.com/i/86130846/spatial-audio-matching">Spatial Audio matching</a></p></li><li><p><a href="https://www.thedododeveloper.com/i/86130846/animated-artwork-on-apple-music">Animated Artwork</a> on Apple Music</p></li><li><p><a href="https://www.thedododeveloper.com/i/86130846/recommended-links">Recommended Links</a></p></li><li><p><a href="https://www.thedododeveloper.com/i/86130846/roadmap">Roadmap</a>: 1st Dec - 14th Dec</p></li></ul><p></p><h2>Catacombs</h2><p>I&#8217;m a big fan of the interactive novels made popular by the <em>Fighting Fantasy</em> and <em>Choose Your Own Adventure</em> books of the &#8216;80s and &#8216;90s. Over the years I&#8217;ve toyed with the idea of making my own digital versions; this interest has only grown with my discovery of <a href="https://www.inklestudios.com/ink/">Ink</a>, a narrative scripting language that powers all of Inkle&#8217;s games such as <em><a href="https://www.inklestudios.com/80days/">80 Days</a></em> and <em><a href="https://www.inklestudios.com/heavensvault/">Heaven&#8217;s Vault</a></em>.</p><p>Whilst I&#8217;m comfortable with Ink and how to create an adventure, the one thing that has always tripped me up is how to do something a bit different with regards to fighting. In a traditional <em>Fighting Fantasy</em> book you typically roll 2d6 plus your skill modifier and compare it to 2d6 plus your opponents skill modifier with the loser taking 2 points of damage. This works well enough but I&#8217;ve never been keen on it mechanically and I wanted to do something else for my own adventures.</p><p>This year, two key games &#8211; <a href="https://www.fellowtraveller.games/citizen-sleeper">Citizen Sleeper</a> and <a href="https://www.marvelsnap.com">Marvel Snap</a> &#8211; made me think of something that could potentially work so last week I decided to throw a prototype together in a few hours as if I were participating in a game jam. The result is <em><strong>Catacombs</strong></em>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UzeS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdfff7cd-7da6-4f6f-84d6-f0932a075576_1339x2716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UzeS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdfff7cd-7da6-4f6f-84d6-f0932a075576_1339x2716.png 424w, https://substackcdn.com/image/fetch/$s_!UzeS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdfff7cd-7da6-4f6f-84d6-f0932a075576_1339x2716.png 848w, https://substackcdn.com/image/fetch/$s_!UzeS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdfff7cd-7da6-4f6f-84d6-f0932a075576_1339x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!UzeS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdfff7cd-7da6-4f6f-84d6-f0932a075576_1339x2716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UzeS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdfff7cd-7da6-4f6f-84d6-f0932a075576_1339x2716.png" width="334" height="677.478715459298" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/fdfff7cd-7da6-4f6f-84d6-f0932a075576_1339x2716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2716,&quot;width&quot;:1339,&quot;resizeWidth&quot;:334,&quot;bytes&quot;:434131,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UzeS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdfff7cd-7da6-4f6f-84d6-f0932a075576_1339x2716.png 424w, https://substackcdn.com/image/fetch/$s_!UzeS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdfff7cd-7da6-4f6f-84d6-f0932a075576_1339x2716.png 848w, https://substackcdn.com/image/fetch/$s_!UzeS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdfff7cd-7da6-4f6f-84d6-f0932a075576_1339x2716.png 1272w, https://substackcdn.com/image/fetch/$s_!UzeS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdfff7cd-7da6-4f6f-84d6-f0932a075576_1339x2716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://testflight.apple.com/join/gOHMi55e&quot;,&quot;text&quot;:&quot;Download Catacombs&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://testflight.apple.com/join/gOHMi55e"><span>Download Catacombs</span></a></p><p>In Catacombs you will fight a number of foes as you delve into the sprawling undercity of Buden<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>. With each encounter you'll see three lanes in front of you similar to the four-card lanes in Marvel Snap. The difference here is that each lane can have up to two dice placed in them. In the first battle you'll only have three dice but by the final boss encounter you'll have six dice available to you as well as a special ability that can be activated with a star faced die. Your enemies also get stronger with several having their own special abilities such as the zombie above being able to convert any of your sixes to a one.</p><p>Once all the dice are placed, the values in each lane are totalled and the loser in each takes a point of damage (a draw in a lane counts as a miss with nobody being injured). Some text will be displayed to show what happened in the fight and then you&#8217;ll repeat the process until one of you is dead. I&#8217;m not a fan of showing specific hit points to players so instead the text is crafted to give you a general inclination as to the current state of yourself and your opponent such as a DM might give in game of <em>Dungeons &amp; Dragons</em>. For example, &#8220;<em>The bandit is looking very pale. They are surely not long for this world.</em>"</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/16856630-dee0-4382-9939-f1081bd4f1ed_1339x2716.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/1ece82d9-5a7c-452d-981d-f435641e523d_1339x2716.png&quot;}],&quot;caption&quot;:&quot;&quot;,&quot;alt&quot;:&quot;&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/69c8d56d-722a-4b96-90cd-d26f44a9d9a8_1456x720.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p>The battle prototype was created in around 3 hours using SwiftUI. It was incredibly quick to mock up as I was able to use the <a href="https://developer.apple.com/sf-symbols/">SF Symbols</a> icon set that Apple provides for all of the game assets such as die, score counters, and even the dotted square outlines. I used the <a href="https://github.com/hellojoelhuber/swiftui-drag-and-drop">SwiftUI Drag-and-Drop package</a> from <a href="https://twitter.com/hellojoelhuber">Joel Huber</a> to get the most challenging part (actually moving the dice around) done in about 20 minutes.</p><p>I cannot overstate how good a feeling it is to be able to imagine something and then have it running on a physical device in under half a day. Whilst you can prototype on paper, I find doing it this way far more rewarding as you can feel it working and make rapid changes; for example, initially it was just one dice per lane but it felt too boring as you were always just waiting to see what numbers you got vs the opponent as you&#8217;d always fill both sides. With two die slots per lane, there is some tactical decision to be made as you don&#8217;t have enough dice to fill every slot; instead you might choose to spread across each lane or pile everything into two slots knowing you&#8217;ll likely take at least one point of damage. On paper I may not have come to that realisation and spent time working on other things before the core mechanic was locked down; doing it as a working prototype meant I got to play it and then fix it almost immediately.</p><p>I wasn&#8217;t planning on doing anything other than a single fight to demonstrate the mechanic but as I was able to get it working so quickly I was able to branch out to six increasingly difficult fights that unlock as you win each battle. I spent around 4 hours adding ~200 lines of text for the damage descriptions and added some graphical elements from an asset pack I&#8217;d previously purchased<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>. I wanted to add some story before each of the fights but the time taken for the damage text drained me of the desire to do any more writing and I knew if I started down that path I'd want to use Ink and start adding choices, etc.</p><p>One thing I&#8217;ve neglected to mention is that this doesn&#8217;t just have to be used for fights but could also pair well with skill checks such as attempting to pick a lock or persuading a bouncer to let you into a VIP lounge. I think there are definitely ways the mechanic could be extended such as retaining any dice you don&#8217;t use for the next round (as if you are winding up for a big hit) and doing more with the special abilities beyond die value manipulation.</p><p>For around 8 hours of work I&#8217;m very pleased with the outcome and definitely think I&#8217;ll be using this idea in my upcoming narrative adventure game, <em><strong>The Dumbwaiter</strong></em><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>. </p><p>You can try out Catacombs right now with this exclusive TestFlight Link:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://testflight.apple.com/join/gOHMi55e&quot;,&quot;text&quot;:&quot;Download Catacombs&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://testflight.apple.com/join/gOHMi55e"><span>Download Catacombs</span></a></p><p><em>* Catacombs will run on any iOS device running iOS 16 or greater. It&#8217;ll also work on any Mac running on Apple Silicon. Apologies, this is just an iPhone test so the design is not optimised for iPad or Mac.</em></p><p></p><p>If you give it a try I&#8217;d love to hear what you think about the dice mechanic, especially if you have suggestions for improvements. Also, do you think it&#8217;s worth wrapping this prototype up in a story and releasing it as a small game?</p><p><strong>Related Links</strong></p><ul><li><p><a href="https://martiancraft.com/blog/2022/05/swiftui-quickstart/">Getting Started with SwiftUI in 30 Minutes or Less</a> - I dismissed SwiftUI back in 2021 after falling foul of its many, <em>many</em>, idiosyncrasies but this article by <a href="https://twitter.com/richturton">Richard Turton</a> made me want to try again. I&#8217;m glad I did! I still don&#8217;t think it&#8217;s production ready but it worked great for this prototype.</p></li><li><p><a href="https://www.eurogamer.net/the-story-of-games-workshop-and-fighting-fantasy">The story of Games Workshop and Fighting Fantasy</a> - An excellent episode of the <a href="https://www.eurogamer.net/topics/one-to-one">One-to-One podcast</a> which dragged me back to the Fighting Fantasy books (including the two new ones released this year).</p></li><li><p><a href="https://www.gameshub.com/news/features/marvel-snap-designer-interview-kent-erik-hagman-smart-card-game-design-31692/">Marvel Snap designer Kent-Erik Hagman talks smart card game design</a> - It&#8217;s always good to hear how game mechanics are created (and what was left on the cutting room floor).</p></li></ul><p></p><h2>Spatial Audio Matching</h2><p>I&#8217;m a big fan of Spatial Audio, but Apple&#8217;s implementation within the Apple Music client leaves a lot to be desired. For example, there is no way to see which of your songs have been upgraded to Spatial Audio. This is mostly because the client doesn&#8217;t know! It only checks when you start playing a song which is why you sometimes get a weird jump from stereo to Spatial Audio a few seconds into a track.</p><p>At WWDC 2022, Apple introduced <a href="https://developer.apple.com/documentation/applemusicapi/songs/attributes?changes=latest_minor">v1.2 of the Apple Music API</a>, a web service that allows you to lookup track information. The thing that caught my eye was the addition of a new <code>audioVariants</code> property that included such things as <em>lossless</em> and <em>dolby-atmos</em><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>. With this I was able to build a rudimentary scraper that could take a list of Apple Music identifiers from my music library and then search the albums to find which tracks had been updated.</p><p>Once that worked, I set it to check through my albums once a week to look for changes and then augmented that with Apple&#8217;s own Spatial Audio playlists. As of now, there are 958,550 tracks in my database<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a> of which 54,739 are tagged with the <em>dolby-atmos</em> variant. The next step is to add <em>your</em> music to the database.</p><p>The way I&#8217;m planning to do this is to update my <a href="https://dodoapps.io/music-tracker/">Music Library Tracker app</a>. This is an app that already has a complete list of all of your songs within it so the steps to enable matching are relatively straightforward:</p><ol><li><p>Update the app so that it gets all of your song ids together and send them to my server where they will be automatically added into the database and checked for Spatial Audio.</p></li><li><p>Let the server respond to this list of ids with a similar list detailing which songs have the <em>dolby-atmos</em> variant.</p></li><li><p>Automatically generate a playlist of these tracks on your device.</p></li></ol><p>With this in place, the dream scenario is that you can open the app, press a button, and within a few seconds have a new playlist generated for you which contains all of your Spatial Audio tracks. There are, however, a few difficulties which is why this isn&#8217;t already running.</p><ul><li><p>Not all of your songs in Apple Music have ids I can use to match against my database. This is especially true if you have a lot of purchased music or tracks created via iTunes Match. It seems to be that the <code>MPMediaLibrary</code> framework on iOS only has identifiers for songs that have been added from the Apple Music service (likely not an issue for newer users but potentially a problem for users with historic libraries). The solution to this is to use string matching (i.e. comparing name, artist, and album against the dataset) but then I could be sending <em>a lot</em> of data for large libraries. This is likely something for a later version once the foolproof matching is working.</p></li><li><p>Depending on your country, your songs may not match my database. At the moment I search for tracks against the UK storefront and then the US one if that fails (as my library is using a UK Apple Music account). I suspect I&#8217;ll need to attempt to match against the US dataset as that will be the largest and then potentially store duplicate information for other countries which will slow down searches, etc. It may be that the first version just supports UK, US, and Canadian accounts.</p></li><li><p>The UI for something like this is fairly difficult as I don&#8217;t want to just have a huge splurge of tracks in a random order but there does need to be some way to browse the tracks that have been updated. Ideally it also needs to be able to tell you what changed since the last time you checked...</p></li><li><p>The final issue is when to do the checking. As Music Library Tracker is a one time purchase it isn&#8217;t feasible to allow for checking to happen in the background all the time in the same way it does for checking your local media as a) it is likely too slow given the number of network requests and b) all the bandwidth usage literally costs me money so I&#8217;d end up paying more than people paid for the app (especially if they bought it on sale 6 years ago when it first launched). I don&#8217;t really want to do subscription pricing for this sort of thing so I think the only option is to have the check be manual from within the app and perhaps limited to once per day. That way you can go and see what songs have been updated when you think to do it and there isn&#8217;t wasted bandwidth being used in the background by every user. Perhaps in the future I could look to adding a cheap subscription for those users who do want to have automatic notifications but that&#8217;s definitely for much further in the future.</p></li></ul><p>So, as you can see there is plenty to think about. I&#8217;m going to tinker with a few bits in the database later this week in order to get the basic matching working within the app and then I can evaluate how easy or difficult the other pieces will be. My aim is to provide a TestFlight link next issue (14th December 2022) that will let you scan your music and see which tracks have Spatial Audio, hopefully with automatic playlist creation as well!</p><p><strong>Related Links</strong></p><ul><li><p><a href="https://www.digitaltrends.com/home-theater/apples-spatial-audio-explained/">What is Spatial Audio?</a> - A good primer from Digital Trends.</p></li><li><p><a href="https://bendodson.com/projects/spatial-audio-finder/">Spatial Audio Finder</a> - My tool which allows you to search for an artist and see which songs are currently supported by Spatial Audio.</p></li><li><p><a href="https://twitter.com/NewSpatialAudio">@NewSpatialAudio</a> - Another one of my tools, this one tweets whenever a new Spatial Audio track is found on Apple Music.</p></li></ul><p></p><h2>Animated Artwork on Apple Music</h2><p>Apple added support for animated artwork within Apple Music towards the end of 2020<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a>. Getting access to that artwork was the number one requested feature for my <a href="https://bendodson.com/projects/apple-music-artwork-finder/">Apple Music Artwork Finder</a> and I was finally able to get it working last month.</p><p>Normally I&#8217;d do some network sniffing to determine what is going on but the Apple Music clients are all protected with SSL pinning which prevents me from seeing what data is being fetched. Thankfully, the Apple Music website also shows the animated artwork and I was able to view the source code to see where it was coming from.</p><p>The website uses JavaScript to load in the video artwork after the page has finished loading; this is a pain as my server can only view the page as it is initially rendered before that extra network request. Luckily, whilst digging through the HTML code I was able to find an embedded JSON string which contained links to the <code>.m3u8</code> playlist that loads the actual videos. All I needed to do was loop through the various urls and then rip out the link to the highest resolution video<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a>.</p><p>Unfortunately this stopped working last week<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-8" href="#footnote-8" target="_self">8</a> as Apple had updated their code to remove that embedded JSON string &#129318;&#127995;&#8205;&#9794;&#65039;. I thought that would be the end of this feature but they&#8217;ve actually made it slightly easier by adding an <code>&lt;amp-ambient-video&gt;</code> tag which links directly to the video playlist. With a quick fix in place to extract the source from that, everything was back to working &#127881;.</p><p>You can start downloading animated artwork now with the <a href="https://bendodson.com/projects/apple-music-artwork-finder/">Apple Music Artwork Finder</a>.</p><h2>Recommended Links</h2><h4>Music</h4><ul><li><p><a href="https://soundcloud.com/andrae-donaldson/dj-hero-i-heard-it-through-the-grapevine-vs-feel-good-inc">I Heard It Through The Grapevine vs. Feel Good Inc.</a> - Incredible mashup from the original DJ Hero game.</p></li><li><p><a href="https://music.apple.com/gb/album/the-playground/1630930454">The Playground by Paper Idol</a> - New album from one of my favourite artists. <em>Sidewalk</em> and <em>Kick Your Friends Out</em> are particularly good tracks.</p></li><li><p><a href="https://music.apple.com/gb/album/thriller/269572838">Thriller by Michael Jackson</a> - Now in Spatial Audio to celebrate the 40th anniversary of this historic album. It also has an animated album cover which doesn&#8217;t loop correctly &#129318;&#127995;&#8205;&#9794;&#65039;</p></li></ul><h4>Video Games</h4><ul><li><p><a href="https://store.steampowered.com/app/427520/Factorio/">Factorio</a> has been updated with <a href="https://factorio.com/blog/post/fff-371">native support for Apple Silicon</a>. The Rosetta version wasn&#8217;t a slouch by any means but the new version runs 20% faster whilst using 15% less battery. Incredible.</p></li><li><p><a href="https://pentiment.obsidian.net">Pentiment</a>, a narrative adventure set in the 16th Century with an art style inspired by illuminated manuscripts and printed woodcuts. I&#8217;m 5 hours or so in and loving it. Available on Game Pass.</p></li><li><p>I&#8217;ve been sucked back into <a href="https://dinopoloclub.com/games/mini-motorways/">Mini Motorways</a> after getting multiple &#8220;<em>Alan beat your high score</em>&#8221; notifications from Game Center. Available on Apple Arcade.</p></li></ul><h4>Board Games</h4><ul><li><p>The Kickstarter campaign for <a href="https://www.kickstarter.com/projects/steamforged/elden-ring-the-board-game">Elden Ring: The Board Game</a> is due to close this week. I&#8217;ve gone for the all-in pledge as it looks phenomenal. The way that battles take place in ring-bound books only to then merge together to build larger boss arenas is something I&#8217;m sure other games will be doing soon.</p></li></ul><h4>Podcasts</h4><ul><li><p><a href="https://podcasts.apple.com/gb/podcast/electronic-wireless-show/id1210446550">Electronic Wireless Show</a> - Ostensibly a podcast about PC gaming, this frequently descends into hilarious off-topic discussions ranging from Nate&#8217;s Gobis to Alice&#8217;s monitoring of the Irish shipping lanes. A podcast I genuinely look forward to every week.</p></li><li><p><a href="https://podcasts.apple.com/gb/podcast/who-shat-on-the-floor-at-my-wedding/id1538165808">Who sh*t on the floor at my wedding</a> - A true story of two brides trying to find out who defecated on the floor at their wedding. Need I say more.</p></li></ul><h4>Books</h4><ul><li><p><a href="https://harpercollins.co.uk/products/how-to-kill-your-family-bella-mackie?variant=39528217182286">How to Kill Your Family</a> by Bella Mackie - An entertaining read which sees antiheroine Grace attempt to murder her entire family. The smart home chapter was fairly implausible but I enjoyed it overall.</p></li></ul><p></p><h2>Roadmap</h2><p>The roadmap is my way of staking what I&#8217;m going to do over the next 2 weeks.</p><h4>1st December - 14th December (Issue #2)</h4><ul><li><p>As mentioned earlier, I need to get the final part of the Spatial Audio matching working with my Music Library Tracker app. A TestFlight beta will be available in the next issue!</p></li><li><p><em><a href="https://restorationgames.com/return-to-dark-tower/">Return to Dark Tower</a></em> is a board game that comes with a physical Bluetooth connected tower and an iPad app that runs several facets of the game. It&#8217;s very cool (in a distinctly nerdy way). I&#8217;ve built a helper app which you can run in a Slide Over panel on iPad to keep track of your cards and Wild Advantages, I just need to put some screenshots together and submit it to the App Store.</p></li><li><p>I&#8217;ve been using a <a href="https://www.elgato.com/en/stream-deck">Stream Deck</a> for the past few months as a way to manage my freelance projects and build tools. I started writing an article about the process but I need to finish it off and publish it.</p></li><li><p>Over the past year I&#8217;ve been working on porting Ink into a nice Swift package. I don&#8217;t think this will be done in the next 2 weeks but I want to at least revisit it as it feels like something I could finish off over the Christmas period.</p></li></ul><p></p><p>That wraps it up for this issue. I hope you found something of interest! Feel free to recommend the newsletter to your friends, family, and colleagues.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.thedododeveloper.com/subscribe?"><span>Subscribe now</span></a></p><p></p><p>You can always comment on this issue directly or email me via <a href="mailto:ben@bendodson.com">ben@bendodson.com</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/p/1-catacombs-spatial-audio-and-animated/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.thedododeveloper.com/p/1-catacombs-spatial-audio-and-animated/comments"><span>Leave a comment</span></a></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>I&#8217;ve reused a fantasy setting I created for a game back in 2020 named <em>Heists</em>. Some of the text on the Introduction page is taken directly from that.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>The asset pack is <a href="https://stevencolling.itch.io/isle-of-lore-2-strategy-figures">Isle of Lore 2: Strategy Figures</a> by <a href="https://stevencolling.com/">Stephen Colling</a>. I&#8217;m also using his new font, <a href="https://stevencolling.itch.io/honey-pigeon">Honey Pigeon</a>.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>Yup, that&#8217;s the name of my next game which will see you operating a dumbwaiter in an old manor house full of mysteries &#128123;. Think <em>Fighting Fantasy</em> meets <em>Papers, Please</em>.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>Many people conflate Spatial Audio and Dolby Atmos but they are different. Spatial Audio is simply a term used by Apple to denote 3D music that can be listened to on their devices (be that via in-built speakers or AirPods) but Dolby Atmos is a specific variant of 3D music. There are several variants in development so Spatial Audio could cover any that Apple chooses to support. At present they only support Dolby Atmos hence the conflation.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>Which is around 1% of the total tracks on Apple Music (although a lot of that 100m total is caused by duplicates across different countries).</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>Animated playlists were in iOS 14 / macOS 11 whilst animated album covers appeared in iOS 14.3 / macOS 11.1</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p>The highest resolution videos then to be a fairly astonishing 2400 x 2400px! I can&#8217;t imagine there is anywhere currently they need a file that large so I guess it&#8217;s just future-proofing for 8K+ displays in the future &#129335;&#127995;&#8205;&#9794;&#65039;</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-8" href="#footnote-anchor-8" class="footnote-number" contenteditable="false" target="_self">8</a><div class="footnote-content"><p>My thanks to <a href="https://twitter.com/Macrike">Macrike</a> for letting me know the finder was broken.</p></div></div>]]></content:encoded></item><item><title><![CDATA[#0: The Setup]]></title><description><![CDATA[In which Ben invites you to view his "In Progress" list]]></description><link>https://www.thedododeveloper.com/p/1-the-setup</link><guid isPermaLink="false">https://www.thedododeveloper.com/p/1-the-setup</guid><dc:creator><![CDATA[Ben Dodson]]></dc:creator><pubDate>Mon, 21 Nov 2022 11:11:01 GMT</pubDate><enclosure url="https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/f90df0e1-9e53-4b6d-a6dc-c5b7fc1c0c69_2316x3088.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Dear Reader,</strong></p><p>Like many developers I have <strong>a lot</strong> of ideas that I start on only to see them go extinct as I move onto the next idea.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://twitter.com/iamdevloper/status/1061897098730311681" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xIT0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fb51fbb4c-bfed-45b3-bbb5-21266410251d_1170x216.png 424w, https://substackcdn.com/image/fetch/$s_!xIT0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fb51fbb4c-bfed-45b3-bbb5-21266410251d_1170x216.png 848w, https://substackcdn.com/image/fetch/$s_!xIT0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fb51fbb4c-bfed-45b3-bbb5-21266410251d_1170x216.png 1272w, https://substackcdn.com/image/fetch/$s_!xIT0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fb51fbb4c-bfed-45b3-bbb5-21266410251d_1170x216.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xIT0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fb51fbb4c-bfed-45b3-bbb5-21266410251d_1170x216.png" width="1170" height="216" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/b51fbb4c-bfed-45b3-bbb5-21266410251d_1170x216.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:216,&quot;width&quot;:1170,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:71824,&quot;alt&quot;:&quot;@iamdevloper: Buying a domain name is buying the wonderful feeling that you'll actually finish the side project you have planned for it.&quot;,&quot;title&quot;:&quot;@iamdevloper: Buying a domain name is buying the wonderful feeling that you'll actually finish the side project you have planned for it.&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://twitter.com/iamdevloper/status/1061897098730311681&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="@iamdevloper: Buying a domain name is buying the wonderful feeling that you'll actually finish the side project you have planned for it." title="@iamdevloper: Buying a domain name is buying the wonderful feeling that you'll actually finish the side project you have planned for it." srcset="https://substackcdn.com/image/fetch/$s_!xIT0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fb51fbb4c-bfed-45b3-bbb5-21266410251d_1170x216.png 424w, https://substackcdn.com/image/fetch/$s_!xIT0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fb51fbb4c-bfed-45b3-bbb5-21266410251d_1170x216.png 848w, https://substackcdn.com/image/fetch/$s_!xIT0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fb51fbb4c-bfed-45b3-bbb5-21266410251d_1170x216.png 1272w, https://substackcdn.com/image/fetch/$s_!xIT0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fb51fbb4c-bfed-45b3-bbb5-21266410251d_1170x216.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>At the moment I&#8217;m:</p><ul><li><p>Actively developing 5 iOS apps, one of which has been worked on periodically for over 6 years and one that just needs me to create screenshots and submit to Apple.</p></li><li><p>Slowly building 2 games along with a dice-based battle system I devised last week that I&#8217;m rapidly prototyping in SwiftUI.</p></li><li><p>Entering the final stages of porting the <a href="https://www.inklestudios.com/ink/">Ink</a> language over to Swift&#8230; then building a &#8220;Choose your own Adventure&#8221; game on top of it.</p></li><li><p>Maintaining the popular <a href="https://itunes.apple.com/us/app/music-tracker-automatic-tracking/id1062583712?ls=1&amp;mt=8">Music Library Tracker app</a> and expanding it to find all of your tracks that have been upgraded to Dolby Atmos (along with the <a href="https://bendodson.com/projects/spatial-audio-finder/">Spatial Audio Finder</a>, the largest database of Atmos tracks available on Apple Music).</p></li><li><p>Making minor updates and fixes to <a href="https://dodoapps.io">my other published apps</a> like <a href="https://apps.apple.com/us/app/browser-note/id1613527249">Browser Note</a> and <a href="https://apps.apple.com/us/app/chaise-longue-to-5k/id1633434180">Chaise Longue to 5k</a>.</p></li><li><p>Looking after my biggest side projects; a number of artwork finders such as <a href="https://bendodson.com/projects/itunes-artwork-finder/">iTunes Artwork Finder</a>, <a href="https://bendodson.com/projects/apple-music-artwork-finder/">Apple Music Artwork Finder</a>, and <a href="https://bendodson.com/projects/apple-tv-movies-artwork-finder/">Apple TV Shows &amp; Music Artwork Finder</a>.</p></li><li><p>Avoiding the 14 items sitting in my &#8220;&#128161; App Ideas&#8221; list.</p></li></ul><p>This is in addition to my <a href="https://bendodson.com/clients/">freelance work</a> which pays my bills and takes up the majority of my time&#8230; until now! Going into 2023 I&#8217;ve decided I want to focus more on getting my own projects out the door and documenting the process that goes into turning vague ideas into actual apps.</p><p>For the past few months I&#8217;ve been meaning to start a newsletter as a way to motivate me to getting projects finished but also as a way of marketing new apps to my existing users. I&#8217;m terrible at marketing and have mostly just launched stuff by putting up a quick post on my blog and tweeting it; this isn&#8217;t great as whilst the people that follow me are more likely to be interested in my other apps, the Twitter algorithm<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> is such that more often than not my followers aren&#8217;t even seeing those announcements.</p><p>I get a lot of email correspondence from users of my apps and websites so this feels like a more natural way to keep you updated as to what I&#8217;m working on. I&#8217;m aiming to send content every 2 weeks which will include roadmaps and details of my development process along with any links or media I&#8217;ve found interesting.</p><p>The first of these will be on <strong>Wednesday 30th November</strong><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> and will include an update on where I am with my Spatial Audio matching along with a preview of the dice-based battle system I&#8217;m currently prototyping.</p><p>Thank you in advance to those of you that subscribe; whether you know me for my apps, games, or artwork finders I&#8217;m sure there will be something in the upcoming issues that will be of interest to you!</p><p><strong>- Ben</strong></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.thedododeveloper.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading The Dodo Developer! Subscribe for free to get updates on all of my apps and projects along with access to exclusive beta tests.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>And Twitter may not be here in it&#8217;s current guise for much longer. I&#8217;m not as pessimistic as others about Twitter&#8217;s short-term future but I can&#8217;t ignore that there is a chance it just disappears overnight.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>As I type this I realise that is my son&#8217;s birthday *<em>makes mental note to complete newsletter the day before</em>*</p></div></div>]]></content:encoded></item></channel></rss>