<?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:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Blog | AM Volume]]></title><description><![CDATA[By Mack Slevin]]></description><link>https://blog.amvolume.com/</link><image><url>https://blog.amvolume.com/favicon.png</url><title>Blog | AM Volume</title><link>https://blog.amvolume.com/</link></image><generator>Ghost 5.80</generator><lastBuildDate>Tue, 07 Apr 2026 09:26:20 GMT</lastBuildDate><atom:link href="https://blog.amvolume.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Watcher: App Intents]]></title><description><![CDATA[<p>I&apos;ve been <a href="https://mastodon.social/@Cavechilde/112582894644620786?ref=blog.amvolume.com" rel="noreferrer">working on an app called Watcher</a>, which checks whether and where a given movie is streaming. I don&apos;t plan on releasing it any time soon, as it currently relies on a data source that I&apos;ve, uh, <em>scraped</em> together, so to speak. (If</p>]]></description><link>https://blog.amvolume.com/6679eebbe5917a0363493385/</link><guid isPermaLink="false">6679eebbe5917a0363493385</guid><dc:creator><![CDATA[Mack Slevin]]></dc:creator><pubDate>Tue, 25 Jun 2024 00:26:11 GMT</pubDate><media:content url="https://blog.amvolume.com/content/images/2024/06/desktop.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.amvolume.com/content/images/2024/06/desktop.png" alt="Watcher: App Intents"><p>I&apos;ve been <a href="https://mastodon.social/@Cavechilde/112582894644620786?ref=blog.amvolume.com" rel="noreferrer">working on an app called Watcher</a>, which checks whether and where a given movie is streaming. I don&apos;t plan on releasing it any time soon, as it currently relies on a data source that I&apos;ve, uh, <em>scraped</em> together, so to speak. (If anybody knows of a non-paid or cheap API, let me know. The ones I&apos;ve found so far have all been those unsettling &quot;don&apos;t worry about the price yet, tell us about yourself first&quot;-types that never return my emails.)</p><p>Anyways, I like over-solving a simple problem, so it&apos;s been fun refining the app and trying out new things with it in my spare time. It being WWDC season, I&apos;ve been watching a lot of session videos lately and using Watcher to try out some of the new frameworks and concepts. If you&apos;ve been watching along too, you might have noticed <a href="https://developer.apple.com/documentation/appintents?ref=blog.amvolume.com" rel="noreferrer">App Intents</a> looming large this year as the underpinning for some of the features announced under the &quot;Apple Intelligence&quot; banner.</p><p>App Intents give you the ability to extend your app to various parts of the system, including Spotlight, the Lock Screen, Control Center, and Siri. It&apos;s a way of encapsulating a piece of functionality and providing it for use <em>&#xE0; la carte</em> outside the context of your app running in the foreground.</p><p>This year, Apple has gone so far as to <em>change their &quot;guidance&quot;</em> on App Intents, which sounds <a href="https://knowyourmeme.com/memes/i-am-altering-the-deal?ref=blog.amvolume.com" rel="noreferrer">vaguely Vader-y</a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/06/guidance.jpg" class="kg-image" alt="Watcher: App Intents" loading="lazy" width="2000" height="558" srcset="https://blog.amvolume.com/content/images/size/w600/2024/06/guidance.jpg 600w, https://blog.amvolume.com/content/images/size/w1000/2024/06/guidance.jpg 1000w, https://blog.amvolume.com/content/images/size/w1600/2024/06/guidance.jpg 1600w, https://blog.amvolume.com/content/images/2024/06/guidance.jpg 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">From the WWDC 2024 Session &quot;Design App Intents for system experiences.&quot; Loving their use of San Francisco Expanded on the slides this year.</span></figcaption></figure><p>Whereas before, the idea was to simply make intents for a handful of only the most crucial features of your app, now they are literally <a href="https://developer.apple.com/wwdc24/10176?ref=blog.amvolume.com" rel="noreferrer">telling you</a> that &quot;anything your app does should be an App Intent.&quot;</p><p>I&apos;ve worked with the App Intents framework before for <a href="https://apps.apple.com/us/app/carpark-spot-marker/id1063194206?ref=blog.amvolume.com">Carpark</a>, but with the above in mind, I thought it might be a good time to start getting in the habit of setting up more intents. Here&apos;s how I implemented my first intent for Watcher.</p><figure class="kg-card kg-code-card"><pre><code>import AppIntents

struct CheckStreamingAvailability: AppIntent {
    static let title: LocalizedStringResource = &quot;Check Streaming Availability&quot;
    static let description: IntentDescription? = &quot;Searches for a movie based on the given title and provides a dialog response describing the movie&apos;s streaming availability. The movie is automatically saved in the background to your collection in Watcher.&quot;
    
    @Parameter(title: &quot;Movie&quot;, description: &quot;The title of the movie for which you want to know the current streaming availability.&quot;)
    var movieTitle: String
    
    @MainActor
    func perform() async throws -&gt; some ProvidesDialog {
        let container = DataCenter.shared.container
        let streamingInfoController = StreamingInfoController()
        let results = try await streamingInfoController.searchResults(forTerm: movieTitle)
        
        if let firstResult = results.first {
            let movie = try await streamingInfoController.itemFromSearchResult(firstResult)
            container.mainContext.insert(movie)
            
            switch movie.streamingServices.count {
                case let x where x &lt; 1:
                    return .result(dialog: &quot;No, the \(movie.year ?? &quot;&quot;) movie \(movie.title) is not currently available for streaming.&quot;)
                case  1...5:
                    return .result(dialog: &quot;Yes, the \(movie.year ?? &quot;&quot;) movie \(movie.title) is currently streaming on the following \(movie.streamingServices.count == 1 ? &quot;service&quot; : &quot;services&quot;): \(movie.streamingServices.keys.joined(separator: &quot;, &quot;))&quot;)
                case let x where x &gt; 5:
                    return .result(dialog: &quot;Yes, the \(movie.year ?? &quot;&quot;) movie \(movie.title) is currently available for streaming on \(movie.streamingServices.count) services.&quot;)
                default:
                    return .result(dialog: &quot;No idea, man. Sorry. Some things we just aren&apos;t meant to know.&quot;)
            }
        } else {
            return .result(dialog: &quot;I&apos;m sorry, I was unable to find a movie by the title \(movieTitle).&quot;)
        }
    }
}
</code></pre><figcaption><p><span style="white-space: pre-wrap;">My intent implementation.</span></p></figcaption></figure><p>One thing that might not be obvious to App Intents newcomers is that this is the entirety of it, by which I mean there is nothing else you need to do aside from defining an <code>AppIntent</code>-conforming struct as shown above. You don&apos;t need to initialize an instance of it anywhere. Once the app is installed, the system will see that this struct exists in your code and it will be automatically made available in the Shortcuts app and elsewhere.</p><p>The title property is required, though the description is optional. The default implementation for each is as a <code>var</code>, though here I&apos;ve changed them to <code>let</code> constants in order to make them work with <a href="https://developer.apple.com/documentation/Swift/AdoptingSwift6?ref=blog.amvolume.com">strict concurrency as enforced by the new Swift 6 language mode</a>.</p><p>While you can leave the description out, I think it&apos;s best to put it to use and err on the side of over explaining. Anecdotally, I&apos;ve found most users I know to have a loose enough grasp on how Shortcuts works that a little hand-holding is appropriate.</p><p>Speaking of descriptions, even if you&apos;ve made App Intents before you may have missed the fact that parameters can have them too. A lot of tutorials and documentation I&apos;ve found skip them, preferring the title-only initializer, and in iOS 18 you can even skip specifying the title entirely, in which case it&apos;ll synthesize one for you based off your variable name. However, like the description for the intent itself, I like providing them for parameters as it&apos;s a quick, easy thing to do which can have outsized benefits in terms of user experience.</p><p>An important point about the <code>movie</code> parameter here is that I very purposefully did <em>not</em> make it optional. You can have optional intent parameters and they are often (if not even <em>usually</em>) the right way to go. If the user provides no value for the optional parameter, the intent will assume a <code>nil</code> value and continue to call the <code>perform</code> method. If a parameter is non-optional, though, the system will proactively stop and ask the user to provide a value if it&apos;s missing, which is exactly what I want here, as this action is pretty useless without a movie to search for.</p><figure class="kg-card kg-code-card"><pre><code>func perform() async throws -&gt; some IntentResult</code></pre><figcaption><p><span style="white-space: pre-wrap;">The `perform` method signature.</span></p></figcaption></figure><p>The <code>perform</code> method is the second and final requirement for an App Intent. The method returns a value conforming to the <code>IntentResult</code> protocol, and the default implementation expects you to return an empty intent result by simply calling <code>.result()</code>. This is great if no user feedback is required after the action is performed.</p><p>If you want to give back something that the user can use, such as an object or value which they can pass along to another Shortcuts action, or just some information about the action that was just performed, you can use one of these other types which conform to IntentResult: ReturnsValue, ProvidesDialog, or ShowsSnippetView.</p><p>For example, returning an Int could look like this:</p><pre><code>func perform() async throws -&gt; some ReturnsValue&lt;Int&gt; {
        .result(value: Int.random(in: 1...10))
}
</code></pre><p>In my case I wanted to take a parameter in the form of a string representing a movie title, use that to perform a search, find the movie, save it to my app&apos;s database and then <em>get back to the user with a text description of the movie&apos;s streaming availability</em> (or lack thereof.) From the user&apos;s perspective, they should be able to just ask Siri about a movie and get a spoken response, with everything else happening invisibly in the background.</p><p><code>ProvidesDialog</code> is perfect for this. It allows you to return a string value which will either be read to the user or display as text on screen depending on the context from which it&apos;s invoked. Once I&apos;ve gotten the needed info here and saved the movie, I use a simple <code>switch</code> statement to create a few different types of response string based on the results.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/06/shortcuts.png" class="kg-image" alt="Watcher: App Intents" loading="lazy" width="2000" height="916" srcset="https://blog.amvolume.com/content/images/size/w600/2024/06/shortcuts.png 600w, https://blog.amvolume.com/content/images/size/w1000/2024/06/shortcuts.png 1000w, https://blog.amvolume.com/content/images/size/w1600/2024/06/shortcuts.png 1600w, https://blog.amvolume.com/content/images/2024/06/shortcuts.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The App Intent as seen in the Shortcuts app.</span></figcaption></figure><p>At this point, the intent is available as a Shortcuts action. The user can use it in their custom workflows, add it to the home screen, etc. This is great, but it still takes some setup from the user. There is one more step we can take to make this even simpler. We can create an <a href="https://developer.apple.com/documentation/appintents/app-shortcuts?ref=blog.amvolume.com" rel="noreferrer">App Shortcut</a>, which acts as a kind of ambassador to the system, spreading the good news of our App Intent. </p><pre><code>import AppIntents

struct WatcherAppShortcutsProvider: AppShortcutsProvider {
    @AppShortcutsBuilder
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: CheckStreamingAvailability(), 
            phrases: [
                &quot;Check streaming availability&quot;,
                &quot;Check streaming on \(.applicationName)&quot;,
                &quot;Check \(.applicationName)&quot;
            ],
            shortTitle: &quot;Check Movie Streaming&quot;,
            systemImageName: &quot;tv&quot;
        )
    }
}
</code></pre><p>The AppShortcutsProvider constructs a collection of all our App Shortcuts. (In this case only the one, of course.)  Each is initialized with a title, an SF symbol name, an instance of intent itself, and a list a phrases which when spoken to Siri will cause it to run your intent, allowing you to make multiple guesses at what your users might naturally say to when trying get at this functionality.</p><figure class="kg-card kg-image-card"><img src="https://blog.amvolume.com/content/images/2024/06/vid.gif" class="kg-image" alt="Watcher: App Intents" loading="lazy" width="231" height="500"></figure><p>Alright, that&apos;s one feature turned into an App Intent! Now for...every other one.</p>]]></content:encoded></item><item><title><![CDATA[Under Cover]]></title><description><![CDATA[<p><a href="https://apps.apple.com/us/app/under-cover-guess-the-album/id6499452629?ref=blog.amvolume.com" rel="noreferrer">Under Cover</a> is an album cover guessing game for music nerds. Something to kill time while waiting for your friend to join you in line at a show. Or, something to pick up every now and then just to remind yourself of your incredible, unparalleled music knowledge. </p><p>It&#x2019;s</p>]]></description><link>https://blog.amvolume.com/663a9624e5917a0363493211/</link><guid isPermaLink="false">663a9624e5917a0363493211</guid><dc:creator><![CDATA[Mack Slevin]]></dc:creator><pubDate>Tue, 14 May 2024 18:20:18 GMT</pubDate><media:content url="https://blog.amvolume.com/content/images/2024/05/post-feature-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.amvolume.com/content/images/2024/05/post-feature-1.png" alt="Under Cover"><p><a href="https://apps.apple.com/us/app/under-cover-guess-the-album/id6499452629?ref=blog.amvolume.com" rel="noreferrer">Under Cover</a> is an album cover guessing game for music nerds. Something to kill time while waiting for your friend to join you in line at a show. Or, something to pick up every now and then just to remind yourself of your incredible, unparalleled music knowledge. </p><p>It&#x2019;s currently available for iPhone and iPad on the <a href="https://apps.apple.com/us/app/under-cover-guess-the-album/id6499452629?ref=blog.amvolume.com" rel="noreferrer">App Store</a> and the code is up on <a href="https://github.com/mackslevin/under-cover?ref=blog.amvolume.com" rel="noreferrer">GitHub</a>.</p><p>These are some notes on my experience making the app and some of the decisions that went into it.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/05/RPReplay_Final1715618186.gif" class="kg-image" alt="Under Cover" loading="lazy" width="231" height="500"><figcaption><span style="white-space: pre-wrap;">Gameplay loop</span></figcaption></figure><p>The gameplay is very simple: Each round, the user watches an obscured album cover slowly reveal itself and tries to guess it as quickly as possible. The quicker the correct guess, the better the score. Users can customize the number of rounds (as well as some other parameters if they delve into the settings screen.) That&apos;s about it.</p><p>I served all roles in the making of the app myself, from concept to design to coding to writing the App Store copy. I wanted to make a small-scale, fun, and modern SwiftUI app with a strong foundation upon which to build. I also wanted to challenge myself on how quickly I could execute. All told, I spent just over 90 hours on it, from creating a new Xcode project to publishing version 1.0 on the App Store. (Though, of course, there is much more work to be done to improve and expand it over time.)</p><h2 id="dev-notes">Dev Notes</h2><p>The app makes use of the <a href="https://developer.apple.com/documentation/MusicKit/?ref=blog.amvolume.com" rel="noreferrer">MusicKit framework</a>, which handles everything from Apple Music audio playback to fetching album metadata. <a href="https://developer.apple.com/documentation/swiftdata?ref=blog.amvolume.com" rel="noreferrer">SwiftData</a> and the <a href="https://developer.apple.com/documentation/Observation?ref=blog.amvolume.com" rel="noreferrer">Observation</a> framework are used for the model and controllers, respectively. It is 100% SwiftUI with a layout that adapts for both iPhone and iPad.</p><p>Before each game the user selects a category, i.e. a pool of albums from which random selections are presented to the user. The <a href="https://github.com/mackslevin/under-cover/blob/main/Undercover/Types/UCAlbum.swift?ref=blog.amvolume.com">album model</a> stores minimal data about an album: album title, artist name, a URL for the album artwork, and the album&apos;s ID on Apple Music, as well as a simple boolean to track whether it&apos;s been favorited by the user.</p><p>I chose to go with an album artwork URL rather than storing the image itself in the model for two reasons. Firstly, storage. Even optimized, those kilobytes can add up quickly! Second, even though album artwork is provided freely with few-to-no qualifications through Apple&apos;s own MusicKit API, from <a href="amvolume.com/apps/in-rotation" rel="noreferrer">past experience</a> I knew that actually <em>storing</em> artwork in-app could risk displeasing app review due to their, um, <em>passionate</em> stance on the copyrighted material of large businesses such as record labels.</p><p>This tied in to another concern, which was that of where the above-mentioned categories would come from in the first place. Having the user add albums manually to construct their own categories would ruin some of the surprise. Adding my own preset categories was something I wanted to avoid, at least for the time being, out of an abundance of caution due to those concerns about copyrighted material. It would be best to have an outside data source for albums which could present them grouped in some way that the user could browse and add from at will.</p><p>Playlists! Apple Music features public-facing playlists created by themselves, users, and outside curators. While the user has to be a subscriber in order to <em>listen</em> to a playlist, they <em>do not</em> in order to just read its metadata. And once we can do that, we can make separate requests to the MusicKit catalog for all the tracks&apos; individual albums with their covers and other metadata &#x2014; everything we need for the model.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/05/IMG_0104.jpeg" class="kg-image" alt="Under Cover" loading="lazy" width="2000" height="762" srcset="https://blog.amvolume.com/content/images/size/w600/2024/05/IMG_0104.jpeg 600w, https://blog.amvolume.com/content/images/size/w1000/2024/05/IMG_0104.jpeg 1000w, https://blog.amvolume.com/content/images/size/w1600/2024/05/IMG_0104.jpeg 1600w, https://blog.amvolume.com/content/images/2024/05/IMG_0104.jpeg 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The playlist search live results interface.</span></figcaption></figure><p>To facilitate this I implemented playlist search with live results presented and updated as the user enters their query. This involved managing multiple asynchronous music catalog resource requests so as to provide a responsive UI with meaningful feedback for the user while avoiding slowing things down.</p><p>After watching <a href="https://www.youtube.com/watch?v=2D05dGo3jB4&amp;t=694s&amp;pp=ygUOcmViZWxvcGVyIG12dm0%3D&amp;ref=blog.amvolume.com">Alex Nagy&apos;s</a> video on MVC in SwiftUI I was eager to try out a similar pattern here. The app uses two controller classes, both made using the <code>@Observable</code> macro and initialized and inserted into the app environment at launch: a single player game controller to manage gameplay and an Apple Music controller to monitor and react to things like subscription status and user authorization.</p><p>After the user has selected a category, set the number of rounds, and initiated gameplay, a <code>GameState</code> enum is initialized and changes to its value determine the user interface.</p><p>In addition to letting the user set the number of rounds before each game, the settings screen features further game customization options, such as a black &amp; white mode for increased challenge. All of these settings are stored in <a href="https://developer.apple.com/documentation/foundation/userdefaults?ref=blog.amvolume.com" rel="noreferrer">User Defaults</a>, with <code>@AppStorage</code> leveraged for use within views. Lately, in most of my projects, I&#x2019;ve taken to creating a simple <a href="https://github.com/mackslevin/under-cover/blob/main/Undercover/Types/StorageKeys.swift?ref=blog.amvolume.com" rel="noreferrer">StorageKeys enum</a> to keep things tidy when going between <code>@AppStorage</code> and accessing UserDefaults directly (the former being a wrapper for the latter.)</p><figure class="kg-card kg-image-card"><img src="https://blog.amvolume.com/content/images/2024/05/IMG_0644.jpeg" class="kg-image" alt="Under Cover" loading="lazy" width="1290" height="2128" srcset="https://blog.amvolume.com/content/images/size/w600/2024/05/IMG_0644.jpeg 600w, https://blog.amvolume.com/content/images/size/w1000/2024/05/IMG_0644.jpeg 1000w, https://blog.amvolume.com/content/images/2024/05/IMG_0644.jpeg 1290w" sizes="(min-width: 720px) 720px"></figure><p>As noted above, all of the MusicKit features covered up to this point are available whether or not the user actually pays for Apple Music. MusicKit&apos;s functionality as a catalog data source only relies on the developer registering the app for the MusicKit service entitlement, setting the proper Info.plist values, and prompting for permission. However, if the user <em>does</em> have an active subscription, Under Cover takes advantage of that to offer the ability to hear the albums as they&apos;re guessing them. To accomplish this, the single player game controller uses the <a href="https://developer.apple.com/documentation/musickit/systemmusicplayer/shared?ref=blog.amvolume.com">system music player singleton</a> and manages its queue as the game progresses, playing a random song from each album during the guessing stage.</p><h2 id="design-notes">Design Notes</h2><p>In line with the decision to move quickly, I did not do extensive wireframing or mockups. Instead, I decided to take some time to think through the app&apos;s needs, come up with a direction and some starting components, and then proceed to &quot;design in code.&quot;</p><p>I&apos;ve found <a href="https://en.wikipedia.org/wiki/Freeform_(Apple)?ref=blog.amvolume.com" rel="noreferrer">Freeform</a> to be a great tool for this kind of conceptual work (and for organizing thought during development overall.) Having a bucket to collect and draw connections between everything from links to graphic inspiration to API documentation pull quotes to sketches and diagrams and to-do lists works very well for the way I think and helps me keep the big picture in mind throughout.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/05/freeform.png" class="kg-image" alt="Under Cover" loading="lazy" width="2000" height="1290" srcset="https://blog.amvolume.com/content/images/size/w600/2024/05/freeform.png 600w, https://blog.amvolume.com/content/images/size/w1000/2024/05/freeform.png 1000w, https://blog.amvolume.com/content/images/size/w1600/2024/05/freeform.png 1600w, https://blog.amvolume.com/content/images/2024/05/freeform.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Diagramming the app at an early stage. It ain&apos;t pretty, and that&apos;s partly the point</span></figcaption></figure><p>I designed the data model in parallel with diagramming the user flow, iteratively determining what data really needed to be stored based on what I want the user to see and do.</p><p>When it came to settling on an overall visual direction I had a few primary points I was considering:</p><ul><li><em>Fun.</em> Games are supposed to feel fun, right? I knew I wouldn&apos;t have time to implement the kinds of shiny, maximalist design techniques I usually see used to convey fun. Maybe I could go more for something irreverent or even, dare I dream, funny.</li><li><em>Staying out of the way.</em> Since the key interaction in the app is studying an album cover as it slowly de-blurs, I wanted to create a neutral environment that wouldn&apos;t fight for attention.</li><li><em>&#x201C;Cool,&#x201D; or something.</em> It was important for me that it looks cool because I like things that look cool! I don&apos;t know why we as designers have decided it would be literal death to ever admit this, but aesthetics &quot;for their own sake&quot; do actually provide value in that humans appreciate aesthetics and it&apos;s nice when things are nice. Also, it&apos;s a music game. People who like music are cool (or at least want to feel that way.)</li><li><em>Quick!</em> Remember, I wanted something I could execute on fast.</li></ul><p>So, I needed to do a lot with a little. I wanted something irreverent and immediate, minimal and to the point. What better a jumping off point then than one of the great music genres of all time, punk rock?</p><figure class="kg-card kg-image-card"><img src="https://blog.amvolume.com/content/images/2024/05/punk-show-posters.png" class="kg-image" alt="Under Cover" loading="lazy" width="1587" height="762" srcset="https://blog.amvolume.com/content/images/size/w600/2024/05/punk-show-posters.png 600w, https://blog.amvolume.com/content/images/size/w1000/2024/05/punk-show-posters.png 1000w, https://blog.amvolume.com/content/images/2024/05/punk-show-posters.png 1587w" sizes="(min-width: 720px) 720px"></figure><p>The classic punk rock show poster has a defined aesthetic based on limitations. Black &amp; white, high contrast. Cheaply printed with images rendered in low quality. Lots of literal &quot;whitespace.&quot; And they were fun! Even the bad ones were charming in their own way.</p><p>This served as a good point of focus. While the end product is in no way meant to literally evoke a show poster, the concept provided some useful constraints and the ingredients for a consistent visual language.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/05/fonts.png" class="kg-image" alt="Under Cover" loading="lazy" width="1060" height="456" srcset="https://blog.amvolume.com/content/images/size/w600/2024/05/fonts.png 600w, https://blog.amvolume.com/content/images/size/w1000/2024/05/fonts.png 1000w, https://blog.amvolume.com/content/images/2024/05/fonts.png 1060w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Type tests</span></figcaption></figure><p>For type, after some trial and error, I landed on the pairing of SF Mono for body and <a href="https://pangrampangram.com/products/nikkei-maru?ref=blog.amvolume.com">PP Nikkei Maru Ultrabold</a> for display. A monospaced font is a perfect fit for the punk poster aesthetic, and Nikkei Maru&apos;s Ultrabold&apos;s forcefulness is just enough of &quot;too much.&quot;</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/05/end-photos.png" class="kg-image" alt="Under Cover" loading="lazy" width="2000" height="475" srcset="https://blog.amvolume.com/content/images/size/w600/2024/05/end-photos.png 600w, https://blog.amvolume.com/content/images/size/w1000/2024/05/end-photos.png 1000w, https://blog.amvolume.com/content/images/size/w1600/2024/05/end-photos.png 1600w, https://blog.amvolume.com/content/images/2024/05/end-photos.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">At the end of each game the user is given a reward/consolation in the form of a lovely jpeg.</span></figcaption></figure><p>As a further tie-in to the poster idea, I applied halftone printing effect to a collection of photos which show when the user completes a game.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/05/prototype-screens.png" class="kg-image" alt="Under Cover" loading="lazy" width="1036" height="904" srcset="https://blog.amvolume.com/content/images/size/w600/2024/05/prototype-screens.png 600w, https://blog.amvolume.com/content/images/size/w1000/2024/05/prototype-screens.png 1000w, https://blog.amvolume.com/content/images/2024/05/prototype-screens.png 1036w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">That Wayne Shorter album rips, btw.</span></figcaption></figure><p>I drafted a couple sections of the app in Figma to give myself a jumping off point. The screen designs here were mostly unaltered in the shipping product and were extrapolated on for the other sections of the app. The simple pill button style became a key component, as did the short-throw drop shadows to highlight album covers and other featured elements.</p><p></p>]]></content:encoded></item><item><title><![CDATA[A Brief, Practical Introduction to Custom Fonts in SwiftUI]]></title><description><![CDATA[<p>We love Apple&apos;s system fonts but sometimes one wants to spread their wings and fly free. Here&apos;s a simple guide to using a custom font in your SwiftUI project. </p><h2 id="demo-project">Demo Project</h2><p>Behold the best albums of last year, objectively. (<a href="https://github.com/mackslevin/top-12-2023?ref=blog.amvolume.com" rel="noreferrer">Project available on my GitHub</a>.)</p><figure class="kg-card kg-image-card"><img src="https://blog.amvolume.com/content/images/2024/04/standard.png" class="kg-image" alt loading="lazy" width="1339" height="2716" srcset="https://blog.amvolume.com/content/images/size/w600/2024/04/standard.png 600w, https://blog.amvolume.com/content/images/size/w1000/2024/04/standard.png 1000w, https://blog.amvolume.com/content/images/2024/04/standard.png 1339w" sizes="(min-width: 720px) 720px"></figure><p>I&apos;</p>]]></description><link>https://blog.amvolume.com/66065fa5ea35864eaa24b366/</link><guid isPermaLink="false">66065fa5ea35864eaa24b366</guid><dc:creator><![CDATA[Mack Slevin]]></dc:creator><pubDate>Mon, 08 Apr 2024 20:49:24 GMT</pubDate><media:content url="https://blog.amvolume.com/content/images/2024/04/two-fonts.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.amvolume.com/content/images/2024/04/two-fonts.png" alt="A Brief, Practical Introduction to Custom Fonts in SwiftUI"><p>We love Apple&apos;s system fonts but sometimes one wants to spread their wings and fly free. Here&apos;s a simple guide to using a custom font in your SwiftUI project. </p><h2 id="demo-project">Demo Project</h2><p>Behold the best albums of last year, objectively. (<a href="https://github.com/mackslevin/top-12-2023?ref=blog.amvolume.com" rel="noreferrer">Project available on my GitHub</a>.)</p><figure class="kg-card kg-image-card"><img src="https://blog.amvolume.com/content/images/2024/04/standard.png" class="kg-image" alt="A Brief, Practical Introduction to Custom Fonts in SwiftUI" loading="lazy" width="1339" height="2716" srcset="https://blog.amvolume.com/content/images/size/w600/2024/04/standard.png 600w, https://blog.amvolume.com/content/images/size/w1000/2024/04/standard.png 1000w, https://blog.amvolume.com/content/images/2024/04/standard.png 1339w" sizes="(min-width: 720px) 720px"></figure><p>I&apos;m pretty happy with the layout above. Let&apos;s see if we can mess it up a little! </p><h2 id="get-a-font">Get a Font</h2><p>First you need a font file (&quot;cool-font.ttf&quot;, &quot;cool-font.otf&quot;, etc.). For the purposes of this demo project I&apos;ll be using <a href="https://pangrampangram.com/products/hatton?ref=blog.amvolume.com" rel="noreferrer">Hatton Bold</a> from the wonderful Pangram Pangram type foundry.</p><h2 id="put-the-font-in-xcode">Put the Font in Xcode</h2><p>Import the font file into your project. (Just drag it in.)</p><figure class="kg-card kg-image-card"><img src="https://blog.amvolume.com/content/images/2024/04/Screenshot-2024-04-08-at-12.35.31-PM.png" class="kg-image" alt="A Brief, Practical Introduction to Custom Fonts in SwiftUI" loading="lazy" width="542" height="500"></figure><p>When importing you may be asked if you want to add this file to your app target(s). You do.</p><p>You will also need to specify the font in your info.plist. Add the key &quot;Fonts provided by application&quot; and make the first value the name of your font file <em>including its file extension</em>.</p><figure class="kg-card kg-image-card"><img src="https://blog.amvolume.com/content/images/2024/04/info-plist.png" class="kg-image" alt="A Brief, Practical Introduction to Custom Fonts in SwiftUI" loading="lazy" width="1506" height="330" srcset="https://blog.amvolume.com/content/images/size/w600/2024/04/info-plist.png 600w, https://blog.amvolume.com/content/images/size/w1000/2024/04/info-plist.png 1000w, https://blog.amvolume.com/content/images/2024/04/info-plist.png 1506w" sizes="(min-width: 720px) 720px"></figure><h2 id="put-the-font-in-a-view">Put the Font in a View</h2><p>To apply this font to a view we just need to use the <code>.font</code> modifier and the <code>Font.custom(_:size:)</code> method, specifying the name of our font <em>without</em> its file extension.</p>
<pre><code>Text(&quot;2023&quot;)
  .font(.custom(&quot;PPHatton-Bold&quot;, size: 56))</code></pre><h2 id="put-the-font-in-the-navigation-bar">Put the Font in the Navigation Bar</h2><p>Applying it to the Navigation Title means we have to interact with a UIKit class, <a href="https://developer.apple.com/documentation/uikit/uinavigationbar?ref=blog.amvolume.com" rel="noreferrer">UINavigationBar</a>, at least for now. (I expect a more SwiftUI-ish way to come about at some point.)</p><p><code>.largeTitle</code> and <code>.inline</code> modes are set separately. In our <code>.onAppear</code> block we&apos;ll create two fonts and apply them.</p><figure class="kg-card kg-code-card"><pre><code>struct HomeView: View {
    @State private var top12: [GoodAlbum] = GoodAlbum.top12of2023
    
    var body: some View {
        NavigationStack {
            VStack {
                // Cool stuff...
            }
            .navigationTitle(&quot;Top 12 Albums&quot;)
            .onAppear {
                let customLargeTitle = UIFont(name: &quot;PPHatton-Bold&quot;, size: 48)!
                UINavigationBar.appearance().largeTitleTextAttributes = [.font: customLargeTitle]
                
                let customInline = UIFont(name: &quot;PPHatton-Bold&quot;, size: 20)!
                UINavigationBar.appearance().titleTextAttributes = [.font: customInline]
            }
        }
    }
}</code></pre><figcaption><p><span style="white-space: pre-wrap;">If force-unwrapping these UIFonts is wrong, I don&apos;t wanna be right.</span></p></figcaption></figure><p>There are plenty of other properties you can change in these text attributes dictionaries, including things like tracking and kerning. Worth poking around. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/04/custom.png" class="kg-image" alt="A Brief, Practical Introduction to Custom Fonts in SwiftUI" loading="lazy" width="1339" height="2716" srcset="https://blog.amvolume.com/content/images/size/w600/2024/04/custom.png 600w, https://blog.amvolume.com/content/images/size/w1000/2024/04/custom.png 1000w, https://blog.amvolume.com/content/images/2024/04/custom.png 1339w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Those numerals &#x1F924;</span></figcaption></figure><h2 id="make-it-nicer">Make it Nicer</h2><p>If you&apos;re like me you&apos;re going to change your mind on the font about 50 times. Let&apos;s create a quick Font extension so that we only have to write/overwrite the font name in one place.</p><pre><code>extension Font {
    static func displayFont(ofSize size: CGFloat) -&gt; Font {
        return Font.custom(&quot;PPHatton-Bold&quot;, size: size)
    }
}</code></pre>]]></content:encoded></item><item><title><![CDATA[SwiftUI: Multiple Buttons in a Single List Row]]></title><description><![CDATA[Just set a button style. Any style, just as long as it's not DefaultButtonStyle.]]></description><link>https://blog.amvolume.com/65fa0be3ea35864eaa24b2e9/</link><guid isPermaLink="false">65fa0be3ea35864eaa24b2e9</guid><dc:creator><![CDATA[Mack Slevin]]></dc:creator><pubDate>Wed, 20 Mar 2024 19:34:37 GMT</pubDate><content:encoded><![CDATA[<p>In SwiftUI, we love Lists. We love Lists so much because we simply must love Lists because <em>SwiftUI</em> loves Lists and, really, if you&apos;ve spent any time in SwiftUI you know you&apos;re better off just learning to love what it loves.</p><p>So, sometimes we put a lot of stuff in a List. You know, really load it up. Maybe we throw in some of our custom views from elsewhere. And this is the part where I usually get caught up and have to re-realize the same thing again. I&apos;ll be tapping or clicking around while testing and notice that one button is triggering the action of another. Buttons within my list are all of a sudden unreliable and fire seemingly random bits of functionality. </p><p>That is because a list row itself functions as a tap target, so each button within in it does not intercept its own taps. So, the row instead funnels all those taps to one of the Buttons, seemingly at random (or maybe there&apos;s an order to it. Don&apos;t @ me, I don&apos;t really care.) Luckily the workaround is simple: <strong>Just set a button style. Any style, just as long as it&apos;s not <code>DefaultButtonStyle</code>.</strong></p><p>Here&apos;s an example where we have three Buttons in the same List row. Each Button sets a different value to a <code>@State</code> variable which defines the color of elements within another of the List&apos;s rows.</p><pre><code>import SwiftUI

struct ContentView: View {
    @State private var brainsColor = Color.primary
    
    var body: some View {
        List {
            Section {
                VStack {
                    Image(systemName: &quot;brain.fill&quot;)
                        .resizable()
                        .scaledToFit()
                        .frame(maxWidth: .infinity, maxHeight: 100, alignment: .center)
                    Text(&quot;BRAINS!&quot;)
                        .tracking(2)
                        .font(.custom(&quot;Silkscreen-Regular&quot;, size: 44))
                }
                .foregroundStyle(brainsColor)
            }
            
            Section(&quot;Brain Color&quot;) {
                HStack(spacing: 44) {
                    Button {
                        brainsColor = .yellow
                    } label: {
                        Circle()
                            .foregroundStyle(.yellow)
                            .frame(width: 44)
                    }
                    
                    Button {
                        brainsColor = .indigo
                    } label: {
                        Circle()
                            .foregroundStyle(.indigo)
                            .frame(width: 44)
                    }
                    
                    Button {
                        brainsColor = .mint
                    } label: {
                        Circle()
                            .foregroundStyle(.mint)
                            .frame(width: 44)
                    }
                }
                .frame(maxWidth: .infinity, alignment: .center)
            }
        }   
    }
}</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/03/not-working.gif" class="kg-image" alt="An animated gif showing the user interface defines above. There is an SF Symbol for a brain and the text &quot;BRAINS!&quot;. There are three buttons to toggle the color of the symbol and text, but as they are clicked only one of the buttons&apos; colors gets applied now matter which is clicked." loading="lazy" width="500" height="339"><figcaption><span style="white-space: pre-wrap;">Please! My buttons! They&apos;re very sick!</span></figcaption></figure><p>As implemented, only one color gets set (the last one) no matter which button you click. However, simply adding an explicit button style to each one allows them to function as expected.</p><pre><code>Button {
    brainsColor = .indigo
} label: {
    Circle()
        .foregroundStyle(.indigo)
        .frame(width: 44)
}
.buttonStyle(PlainButtonStyle()) // OK, List, does this make you happy?!?</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/03/working.gif" class="kg-image" alt="The same interface from before: Three buttons which toggle the color of a brain image and some text. This time the buttons work!" loading="lazy" width="500" height="339"><figcaption><span style="white-space: pre-wrap;">That&apos;s satisfying.</span></figcaption></figure><p>Weird!</p>]]></content:encoded></item><item><title><![CDATA[Buy Our E-Book to Turbo-Juice Your Thinkingfulness]]></title><description><![CDATA[<p><a href="https://37signals.com/23?ref=blog.amvolume.com">The 37 Signals website</a>:</p>
<blockquote>
<p>Thoughting vs. thinking<br>
Long-term planning is what you thought. Short-term planning is what you think.</p>
</blockquote>
<p>Can you imagine a more soul-destroyingly embarrasing company?</p>]]></description><link>https://blog.amvolume.com/65ea05d9ea35864eaa24b227/</link><guid isPermaLink="false">65ea05d9ea35864eaa24b227</guid><dc:creator><![CDATA[Mack Slevin]]></dc:creator><pubDate>Wed, 06 Mar 2024 00:35:13 GMT</pubDate><content:encoded><![CDATA[<p><a href="https://37signals.com/23?ref=blog.amvolume.com">The 37 Signals website</a>:</p>
<blockquote>
<p>Thoughting vs. thinking<br>
Long-term planning is what you thought. Short-term planning is what you think.</p>
</blockquote>
<p>Can you imagine a more soul-destroyingly embarrasing company?</p>
]]></content:encoded></item><item><title><![CDATA[Fake Album Covers]]></title><description><![CDATA[<p>My initial submission of In Rotation included screenshots featuring album covers from some favorite artists of mine. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/03/1.png" class="kg-image" alt="App Store screenshots for the app In Rotation. Album covers from David Bazan, Madeline, Kenney, Wheat, and Protomartyr are shown." loading="lazy" width="2000" height="2114" srcset="https://blog.amvolume.com/content/images/size/w600/2024/03/1.png 600w, https://blog.amvolume.com/content/images/size/w1000/2024/03/1.png 1000w, https://blog.amvolume.com/content/images/size/w1600/2024/03/1.png 1600w, https://blog.amvolume.com/content/images/size/w2400/2024/03/1.png 2400w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">If you&apos;ve yet to listen to Madeline Kenney I pity you.</span></figcaption></figure><p>This, of course, triggered a rejection. I needed explicit, verifiable permission from rights holders. I was not surprised but I&</p>]]></description><link>https://blog.amvolume.com/65ea05d9ea35864eaa24b226/</link><guid isPermaLink="false">65ea05d9ea35864eaa24b226</guid><dc:creator><![CDATA[Mack Slevin]]></dc:creator><pubDate>Tue, 05 Mar 2024 23:01:39 GMT</pubDate><content:encoded><![CDATA[<p>My initial submission of In Rotation included screenshots featuring album covers from some favorite artists of mine. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/03/1.png" class="kg-image" alt="App Store screenshots for the app In Rotation. Album covers from David Bazan, Madeline, Kenney, Wheat, and Protomartyr are shown." loading="lazy" width="2000" height="2114" srcset="https://blog.amvolume.com/content/images/size/w600/2024/03/1.png 600w, https://blog.amvolume.com/content/images/size/w1000/2024/03/1.png 1000w, https://blog.amvolume.com/content/images/size/w1600/2024/03/1.png 1600w, https://blog.amvolume.com/content/images/size/w2400/2024/03/1.png 2400w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">If you&apos;ve yet to listen to Madeline Kenney I pity you.</span></figcaption></figure><p>This, of course, triggered a rejection. I needed explicit, verifiable permission from rights holders. I was not surprised but I&apos;d been hopeful, as I&apos;d found various other apps in my category use album covers from artists as big as Beyonc&#xE9; in their App Store screenshots. (And like, come on, I know that single-developer app didn&apos;t wrangle written permission from Bey&apos;s team for their niche Apple Music utility&apos;s App Store screenshots.)</p><p>Naturally, then, I settled down with a can or four of costly IPA, an image generation AI, and my best friend Pixelmator Pro and put together some fake album covers which I hoped would still allow me to show off my interface a good light. Weirdly this still triggered a rejection, and for the same stated reasoning! (I eventually ended up publishing everything under a CC0 public domain dedication license to get around this. So, hey, feel free to have your way with these jpegs!)</p>
<p>I present a selection of them to you here now, because I&apos;m more proud of them than I should be and because what&apos;s more funny than explaining jokes?</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/03/moof.jpg" class="kg-image" alt="An album cover featuring an ethereal figure wrapped in silk with their face obscured. The text &quot;M O O F&quot; is displayed." loading="lazy" width="1024" height="1024" srcset="https://blog.amvolume.com/content/images/size/w600/2024/03/moof.jpg 600w, https://blog.amvolume.com/content/images/size/w1000/2024/03/moof.jpg 1000w, https://blog.amvolume.com/content/images/2024/03/moof.jpg 1024w" sizes="(min-width: 720px) 720px"><figcaption><i><em class="italic" style="white-space: pre-wrap;">M O O F</em></i><span style="white-space: pre-wrap;"> by Claruus</span></figcaption></figure><p>I like to think of Claruus as an early-90s New Age group, the kind you&apos;ve never heard of but then you find out they won eight Grammys, sold 15 millions records, and still sell out Red Rocks every summer.</p><p>This, of course, is a reference to <a href="https://en.wikipedia.org/wiki/Dogcow?ref=blog.amvolume.com" rel="noreferrer">our favorite dogcow</a>. For those uninitiated, I refer you to the <a href="https://512pixels.net/dogcow/?ref=blog.amvolume.com" rel="noreferrer">great Clarus historian Stephen Hackett</a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/03/courage.png" class="kg-image" alt="An album cover featuring a digitally manipulated statue&apos;s head and the text &quot;Courage&quot;" loading="lazy" width="1024" height="1024" srcset="https://blog.amvolume.com/content/images/size/w600/2024/03/courage.png 600w, https://blog.amvolume.com/content/images/size/w1000/2024/03/courage.png 1000w, https://blog.amvolume.com/content/images/2024/03/courage.png 1024w" sizes="(min-width: 720px) 720px"><figcaption><i><em class="italic" style="white-space: pre-wrap;">Courage</em></i><span style="white-space: pre-wrap;"> by Headphone Jackson</span></figcaption></figure><p>And who could forget the seminal post-rock opus <em>Courage</em> by Headphone Jackson? I saw them open for Mission of Burma once and my ears are still ringing. </p><p>This is a reference to our favorite Apple Fellow and his <a href="https://www.theverge.com/2016/9/7/12838024/apple-iphone-7-plus-headphone-jack-removal-courage?ref=blog.amvolume.com" rel="noreferrer">onstage remarks</a> about the reasoning for removing the headphone jack.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.amvolume.com/content/images/2024/03/nil-optional.jpg" class="kg-image" alt loading="lazy" width="944" height="944" srcset="https://blog.amvolume.com/content/images/size/w600/2024/03/nil-optional.jpg 600w, https://blog.amvolume.com/content/images/2024/03/nil-optional.jpg 944w" sizes="(min-width: 720px) 720px"><figcaption><i><em class="italic" style="white-space: pre-wrap;">this doesnt exist</em></i><span style="white-space: pre-wrap;"> by Nil/Optional</span></figcaption></figure><p>Your parents had this 70s prog rock classic in their collection when you were a kid but you didn&apos;t really <em>get it</em> until you got older and discovered drugs.</p>]]></content:encoded></item><item><title><![CDATA[The Shiny Newness of SwiftData]]></title><description><![CDATA[<p>I make the mistake of jumping on the shiny new thing a lot. I say &quot;mistake&quot; but it is completely on purpose and I intend to continue the pattern. I use CSS features with limited browser support. If you have a new video streaming service featuring even one</p>]]></description><link>https://blog.amvolume.com/65ea05d9ea35864eaa24b225/</link><guid isPermaLink="false">65ea05d9ea35864eaa24b225</guid><dc:creator><![CDATA[Mack Slevin]]></dc:creator><pubDate>Tue, 05 Mar 2024 02:26:41 GMT</pubDate><content:encoded><![CDATA[<p>I make the mistake of jumping on the shiny new thing a lot. I say &quot;mistake&quot; but it is completely on purpose and I intend to continue the pattern. I use CSS features with limited browser support. If you have a new video streaming service featuring even one thing I&apos;ve heard of you will receive at least 12 months of payments from me before I admit to myself you&apos;re garbage. I put a lot of time into <a href="https://en.wikipedia.org/wiki/ITunes_Ping?ref=blog.amvolume.com">Ping</a>.</p>
<p>This shiny thing jumping urge is strong each year during WWDC, when Apple shows off their new APIs. I always look forward to going through the session videos, seeing the beautiful work these hyper-talented nerds have done, hoping the next new API I stumble upon is going to unlock a new app idea or feature, or remove some giant pain point I&apos;ve forgotten I even have.</p>
<p>SwiftData was easily the thing I was most excited about this year, the one I wanted to be <em>the thing</em>. I did not have any significant problems with CoreData, but it was never work I was particularly excited about doing. The idea of a more modern approach to working with your data models on Apple platforms was enough to get me mentally committed to using it essentially once I heard the name.</p>
<p>It wasn&apos;t without some bumps in the road. For example, there was a good portion of the summer where you couldn&apos;t actually delete things, which isn&apos;t what you want. But things eventually got ironed out and by the time I decided to revive and (completely rewrite) <a href="https://apps.apple.com/us/app/carpark-spot-marker/id1063194206?ref=blog.amvolume.com">Carpark</a> this past January, SwiftData was an easy choice.</p>
<p>Now with <a href="https://apps.apple.com/us/app/in-rotation/id6471543369?ref=blog.amvolume.com">In Rotation</a> I&apos;ve shipped my second app using a technology that didn&apos;t exist publicly before last summer.</p>
<p>Hmm. I probably shouldn&apos;t stare at that sentence for too long.</p>
<p>But for now I&apos;m happy I jumped on that shiny thing! It&apos;s nice when that works out.</p>
]]></content:encoded></item></channel></rss>