Categories
Review

“Fine Structure”

Sam Hughes

I actually read this almost immediately after “Ed”, and it makes for a heck of contrast. Where “Ed” is mostly pretty light and quick, this is all kinds of convoluted in terms of what’s going on with the plot. But it also does that thing that Hughes does really well—it spans a massive amount of time and space, and covers a staggering amount of ground.

The basic concept is truly excellent, though: what if, every time you tried some Cool Science Fiction Thing, it worked—once. And then never again.

It’s a really interesting constraint for a work of science fiction, as well: how can you do enough Cool Science Fiction Things for a full book, when the core concept is that those concepts are consumable? It makes for, as I said, a sprawling world that must be built out—larger than “Ed” or “Ra” had to be, and bringing in some definite “higher-dimensional beings would look a lot like Cthulhu, wouldn’t they?” energy.

The end result is, I can’t recommend this as readily as I did “Ed”, because it’s boggling at times. But it’s a great payoff, and ties together things you wouldn’t at all expect, much better than you’d expect. If that sounds good to you, give it a read.

Categories
Review

“Ed”

Sam Hughes

I’m reading Hughes’ work all out of order, but happily, there’s really no shared universe from story to story, so it’s not an issue. And, having now read all four of his books, I can say that “Ed” would be my recommended starting place, if you want to give it a go.1 It’s the lightest – a very silly beginning, and told by a narrator who’s further outside the story than any of his other works. While it’s not quite to that space opera feel, with the big events happening in the background and the story following regular-sized people just trying to make it through, it’s more human in scale.

It’s also very episodic most of the way through, so it’s easier to pick up and put back down for a while, if that’s your reading style, though there’s enough callbacks that you’ll be rewarded for going right through. (And, because this is a web-first piece of fiction, if you’re reading it online, a great deal of those callbacks are hyperlinks to the correct chapter – an easy way to catch yourself back up without devolving into “as you know, Bob” territory.)

All in all, “Ed” is a fairly short read, and a fun one. It’s got that characteristic “sprawling across space and time” feel that’s characteristic of Sam Hughes, but at no point do you feel like you need to stop and take notes to try to follow what’s going on. I quite enjoyed it. Give it a read, and if it really captures your interest, buy a copy!

  1. I’ve previous reviewed “Ra”, and will at some point write up my thoughts on “Fine Structure”, but my stance on “There Is No Antimemetics Division” is the simple “if you like SCP, you’ll like this; if you don’t know what that means, this isn’t a good starting point.”
Categories
Programming

Dev Blogs

I recently had someone ask for recommendations for dev blogs to fill out their RSS reader. After going through what I have in my RSS reader, I realized that this would make a good little post—these have all been helpful resources to me, so it seems likely they could be helpful to someone else. (I have also been informed by a friend of mine that I should include myself in this list, but I figure you’re already here. Go ahead and throw this site URL into your RSS reader, the feed is nice and discoverable.)

So, in no particular order, developer blogs:

  • Use Your Loaf posts fun little things, usually regarding iOS development.
  • The Always Right Institute does terrible, terrible things with Swift, and they’re consistently a delight to read.
  • SwiftLee writes very solid articles explaining various Swift language features, iOS API things, and general career tips.
  • Swift with Majid does weekly deep dives into SwiftUI… and may or may not be the inspiration for my recent spate of programming posts.
  • Swift by Sundell is one of the definitive Swift sites; not only does John Sundell write up plenty of great articles (see ‘Swift Fundamentals’ as a great starting point!), he also has a couple podcasts, if listening is more your style.
  • Reda Lemeden does some fun explorations of Swift, as well as more general things with his “This Week I Learned” posts.
  • Gui Rambo does some really neat explorations of what’s possible on iOS when he’s not cracking open the developer betas to see what features Apple forgot to feature-flag out of the build.
  • Povilas Staškus hasn’t posted in a while, but his posts are worth a back-read; the Jekyll-to-Publish migration was a fun one. And, hey, putting an infrequent poster in RSS is what RSS is for!
  • NSHipster is a venerable name in the iOS dev world; not as actively updated as it once was, but the back catalog is extremely solid, and will quite often be one of the first results when Googling various things.
  • Kristaps Grinbergs writes in a similar vein to Swift with Majid, with explanations of various parts of the SwiftUI API.
  • Julia Evans is a break from my very iOS-heavy list so far; she does comics and zines explaining general programming concepts, command line tools, and all sorts of stuff. Seriously good explanations, I recommend checking them out.
  • Brent Simmons blogs about NetNewsWire, life as a developer, and neat bits of history in the Apple world.
  • Steven Troughton-Smith may well be the expert on Catalyst apps; his recent post of various sample apps is a wonderful resource if you’re looking into bringing an iPad app to macOS.
  • Erica Sadun’s blog has recently been a lot of neat macOS automation stuff; she’s also a frequent contributor to Swift itself.
  • On Hacking With Swift, Paul Hudson provides excellent WWDC coverage and an amazing array of tutorials and documentation.1 If you’re just getting started, go through his free 100 Days of Swift and 100 Days of SwiftUI courses.

And, in addition to all those blogs, I also funnel email newsletters into RSS whenever possible. My top picks from that list are:

  • iOS Goodies provides a little commentary with a list of articles.
  • iOS Dev Weekly comes with much more commentary; I’m also a fan of the “and finally…” section, it’s a nice ending every week.
  1. Seriously, it’s so robust that one of my top bangs is !hws
Categories
Programming

Network Caching With Combine

I’m going to lead in to this post by saying that this isn’t a Definitively Correct way to do this; it works for my use case, but has some definitive issues. Still, in the interest of transparency—and not running out of content for this blog—I’m going to share it anyways.

Last week, I shared a fun little enum-based way of representing API endpoints in a type-safe manner. It’s also pretty easy to expand on to use as the key for an in-memory cache of endpoint results.

(This, by the by, is Caveat 1: it’s a purely in-memory cache, with nothing being persisted to disk. User quits out of your app? Cache is gone. For what I’m working on, though, this is the behavior we want, so it works well.)

Let’s get started with the actual API surface we want:

class ApiClient { func get<T: Decodable>(endpoint: Endpoint, reset: Bool = false) -> T? { ... } }
Code language: Swift (swift)

Now, the funky thing about how I’ve got this implemented is that this get method is idempotent – if you call it 10,000 times for the same Endpoint, it’s only going to spit out one network request, and will just continue returning nil until something is actually present. (Well, unless you’re setting reset to true – that’s really in there for debug purposes more than anything else. Give the API I’m using on this project, it’s almost never necessary.)

And, for use with SwiftUI, we want it to be called again when the network request finishes, so we’ll make it an ObservableObject.

Next, let’s work out how to store things to achieve that. The main thing we need is somewhere to store the network requests, and somewhere to store the results – the aforementioned in-memory cache. And, to get that “call the method again when the cache changes” behavior, we can make it @Published:

class ApiClient: ObservableObject { private var loaders: [Endpoint:AnyCancellable] = [:] @Published private var cache: [Endpoint:Decodable] = [:] }
Code language: Swift (swift)

Now, to make Endpoint we need it to be viable dictionary key, which is pretty easy to accomplish:

extension Endpoint: Hashable { }
Code language: Swift (swift)

Finally, we need to implement the actual method. Without further ado:

class ApiClient: ObservableObject { private var loaders: [Endpoint:AnyCancellable] = [:] @Published private var cache: [Endpoint:Decodable] = [:] func get<T: Decodable>(endpoint: Endpoint, reset: Bool = false) -> T? { if reset { cache[endpoint] = nil loaders[endpoint] = nil // which implicitly calls loaders[endpoint]?.cancel(), via the deinit } if let _item = cache[endpoint], let item = _item as? T { print("\(endpoint): cache hit") return item } if loaders[endpoint] == nil { print("\(endpoint): cache miss; beginning load") loaders[endpoint] = URLSession.shared.dataTaskPublisher(for: endpoint.url) // 1 .map(\.data) .decode(type: T.self, decoder: JSONDecoder()) // 2 .receive(on: DispatchQueue.main) .sink(receiveCompletion: { (completion) in print(completion) }, receiveValue: { (item) in self.cache[endpoint] = item }) } print("\(endpoint): cache miss; loading already in progress") return nil } }
Code language: Swift (swift)

Two notes in there:

  1. You may want to swap out the use of URLSession.shared here for a parameter into the class’ constructor – it’ll make your testing a bit easier.
  2. Even more so than (1), you probably want to swap out JSONDecoder() here for something stored on the class – that decoder isn’t free to initialize!

Now, as I mentioned, this has some limitations. The first one I already went over – it’s a purely in-memory cache. The second is at the call site – since this is generic across Decodable, you have to annotate at the call site what the expected return type is, which isn’t the most ergonomic.

My actual thought on fixing that was very TypeScript-inspired – creating overloads that specify T based on the given endpoint. In Swift, this isn’t too difficult, just adding something like:

extension ApiClient { func getCategory(_ id: Category.ID, reset: Bool = false) -> Category? { get<Category>(endpoint: .category(id), reset: reset) } }
Code language: Swift (swift)

Which, of course, can get a bit repetitive depending on the number of endpoints you have. But hey, it works for my use case, and it may be helpful to someone else, so: blog post!

As a post-credits scene of sort, the TypeScript version is a bit silly, and takes advantage of the absolutely bonkers way method overloads work in TypeScript. I’ll throw out some pseudocode:

class ApiClient{ func get(endpoint: Endpoint.Category, reset: bool): Category? func get<T>(endpoint: Endpoint, reset: bool: T? { ... } }
Code language: TypeScript (typescript)

Yes, method overloads like this are just declaring the method multiple times before the actual body. And yes, you can specify an individual enum case as a type.

Categories
Playlist

Playlist of the Month: June 2021

This month, Dolby Atmos suddenly became a thing in the streaming music world, and I’ve been having fun with it! If you can, give it a try, it’s neat. (Admittedly, most of the discernible difference is just a matter of “this is a nice remaster,” but still.)

Cologne – Haux on Something to Remember – EP

Slowly – ODIE on Slowly – Single

How It Was – Yoste on A Few Brief Moments – EP

Come On – Will Young on Echoes

We’ll Be Alright – Yoste on A Few Brief Moments – EP

DON’T TELL THE BOYS – Petey on Checkin’ Up on Buds – EP

Spaces – Jaymes Young on Spaces – Single

Arcade – Duncan Laurence on Arcade – Single

twentyfive – Yoste on twentyfive – Single

Lightning – Hayden Calnin on Lightning – Single

Oh Dear, Oh Beaux – beaux on A Love Letter To the Moments Spent Outside

I Can’t Lose You – Isak Danielson on Tomorrow Never Came

MONTERO (Call Me By Your Name) – Lil Nas X on MONTERO (Call Me By Your Name) – Single

On My Own (feat. Kid Cudi) – Jaden on ERYS

Neon Medusa – The Midnight on Horror Show – EP

Flow – Vide on Flow – Single

Into Your Arms – Vide on Into Your Arms – Single

Starlite – HOKO on Heathen

DIM – SYML on DIM – EP

BLACK TEETH – SYML on DIM – EP

Magic – Phillip LaRue on Night Swimming – Single

I Took a Pill in Ibiza, Youth (Acoustic Mashup) [feat. Chasing Taylor] – Landon Austin on Covers: Six

Astronaut In The Ocean – Masked Wolf on Astronaut In The Ocean – Single

Rät – Penelope Scott on Public Void

Good in Red – The Midnight on Horror Show – EP

Somewhere We Can Be Alone – Gunnar Gehl on Somewhere We Can Be Alone – Single

Symphony – Forester on Symphony – Single

The Stranger – The Midnight on Horror Show – EP

Ghost in Your Stereo – The Midnight on Horror Show – EP

Coldplay (feat. Vic Mensa) – Mr Hudson on Coldplay (feat. Vic Mensa) – Single

Save Me – Majik on It’s Alright / Save Me – Single1

Tiempo – Ozuna on Tiempo – Single

Attached – Shawn James on Attached – Single

Hold Me – Lani Rose on To: Keep You From: Hurting Me

Hell On Earth – Turbo on Hell On Earth – Single

Can’t Work You Out – Nathan Ball on Can’t Work You Out – Single

All We Have Is Now (feat. Trixie) – Blewbird on All We Have Is Now (feat. Trixie) – Single

False Confidence – Noah Kahan on Busyhead

Expectations – Joel Ansett on Expectations – Single

Magazines – Anson Seabra on Magazines – Single

Blinding Lights – The Weeknd on After Hours

In Your Eyes – The Weeknd on After Hours

Paranoid – Lani Rose on To: Keep You From: Hurting Me

Imported – Jessie Reyez & 6LACK on Imported – Single2

Belong – slenderbodies on Komorebi

u love u – blackbear & Tate McRae on u love u – Single

Revolver – bülow on Revolver – Single

Goosebumps – HVME on Goosebumps – Single

There Is A Light That Never Goes Out – The Smiths on The Queen Is Dead3

Never Surrender (feat. H. Kenneth) – Mike Emilio & Hypanda on Never Surrender (feat. H. Kenneth) – Single

Sunflower (Spider-Man: Into the Spider-Verse) – Post Malone & Swae Lee on Hollywood’s Bleeding

Late At Night – Roddy Ricch on Late At Night – Single

Like That – JP Saxe on Dangerous Levels of Introspection

Don’t Go Puttin Wishes in My Head – TORRES on Thirstier4

Todo Estaba Bien – Carlos Sadness & Manuel Medrano on Tropical Jesus

My Rajneesh – Sufjan Stevens on America – EP

Baci Dalla Tunisia – Mahmood on Ghettolimpo

Talata – Mahmood on Ghettolimpo5

T’Amo – Mahmood on Ghettolimpo

Karma – Mahmood & Woodkid on Ghettolimpo

Klan (Spanish Version) – Mahmood & DRD on Ghettolimpo

Kobra – Mahmood on Ghettolimpo

Out in LA – Aquilo on Out in LA – Single

I Only Go South – Yoste on I Only Go South – Single

GROWING PAINS – AJ Mitchell on GROWING PAINS – Single

Dinero – Trinidad Cardona on Dinero – Single

In the End – Linkin Park on Hybrid Theory6

Player 2 – Rory Webley on Player 2 – Single

Under the Covers (feat. DT James) – Blewbird on Under the Covers (feat. DT James) – Single

Phonky Town – Playaphonk on Phonky Town – Single7

b!!!rds – Sir Sly on The Rise & Fall of Loverboy

Gone Are The Days (feat. James Gillespie) – Kygo on Gone Are The Days (feat. James Gillespie) – Single

Los Tontos (Live at NPR’s Tiny Desk) – C. Tangana & Kiko Veneno on El Madrileño (Live at NPR’s Tiny Desk) – Single8

Ride – Amber Run on Ride – Single

Instant Crush (feat. Julian Casablancas) – Daft Punk on Random Access Memories

bad guy – Billie Eilish on WHEN WE ALL FALL ASLEEP, WHERE DO WE GO?

All Eyes On Me – Bo Burnham on Inside (The Songs)9

Cherry – Chromatics on Cherry (Deluxe)

Bezos I – Bo Burnham on Inside (The Songs)

August – Flipturn on Citrona

Welcome to the Internet – Bo Burnham on Inside (The Songs)

Distorted Light Beam – Bastille on Distorted Light Beam – Single

  1. if I listen to Majik enough, will they come back? I can dream
  2. “Some people call me six-lack”
  3. It’s really neat living in the future, because this song popped into my head the other day, and I just announced to the air that I wanted to hear it, and then, suddenly, there it was.
  4. This feels like my childhood, but in a way that doesn’t evoke nostalgia, somehow? It’s a very strange feeling, but I quite like it.
  5. I really enjoyed this whole album; I think Talata is my favorite, just because it’s the weirdest.
  6. I think this is the best Atmos remaster I’ve listened to so far.
  7. “This sounds like demons trying to tell you something.” – my sister
  8. This sounds like it shouldn’t be possible, what with the pandemic still ongoing, but it’s a delightful piece of music.
  9. If you haven’t yet watched Inside on Netflix, I heartily recommend it — it’s an incredible work of art. The fact that I got this song out of it, which is one of my favorites this month, is a nice plus.
Categories
Programming

Safe API Calls with Enums

I really hate runtime errors. I’m a firm believer in the concept that as many errors as conceivably possible should be caught at compile-time. It makes sense, after all: a compile time error for me means a few minutes of my time spent fixing it; a runtime error for my users means a few seconds of their time staring at an error… multiplied across however many users there may be. And, really, how often are you working on an app where you outnumber the users?

In this vein, I’ve been tinkering with how to handle network requests. The easy first thing to do was to swap out stringly-typed URLs for an enum, and Swift’s associated values make this a breeze:

enum Endpoint { case globalConfiguration case category(Category.ID) case product(Product.ID) ... }
Code language: Swift (swift)

Your definitions may vary – this is, of course, very specific to the actual use case. But look at that – not only do we have clear representations of the various endpoints available, we can even link the variable components of those URLs to being the proper type for the model we expect to get back. How’s that for self-documenting code?

Next, we need a way to convert from this lovely enum to the actual URL we’re going to use to make our requests. I’ve split this up a little bit:

extension Endpoint { var pathComponents: String { switch self { case .globalConfiguration: return "/config.json" case .category(let ID): return "/category/\(ID).json" case .product(let ID): return "/product?id=\(ID)" ... } } var url: URL { #if DEBUG return URL(string: "https://dev.my.app")!.appendingPathComponent(pathComponents) #else return URL(string: "https://api.my.app")!.appendingPathComponent(pathComponents) #endif } }
Code language: Swift (swift)

Et voila, converting to our known URLs, and a nice little “automatically use the dev server in debug mode” check, while we’re at it.

Now, depending on how you’re organizing your networking code, we can tweak the API surface here a little bit to make things clearer. Add those extensions in the same file as the code that makes the network requests, and declare them fileprivate – and now, you’ve got an automatic reminder from the compiler if you start writing some URL-based networking code outside of the network component. No more leaky abstraction!

Categories
Technology

WWDC21

I’m writing this a bit after WWDC has ended, and publishing it a bit further after that, so pardon the tardiness as I share my thoughts, in no particular order:

  • As a user, I am quite excited by the new functionality in FaceTime, and the new Focus system for managing notifications. The system of tiered notifications looks great, and I’m excited for the, oh, 15 minutes most people will have between ‘installing iOS 15’ and ‘an app first abusing the notification level to send them spam.’1 In iOS 16, can we get a “report notification as spam” button that immediately flags the app for App Review to yell at?
  • As both a developer and user, I was immensely excited to see the ManagedSettings and ManagedSettingsUI frameworks – I really like the concept of Screen Time for externalized self-control, and being able to use custom implementations or build my own seemed like a dream.2 Unfortunately, all those dreams were immediately crushed by the reveal that this all requires FamilyControls, so not only can I not do anything interesting with it as a developer, I can’t even use it myself. Because, as we all know, the only use case for “my phone won’t let me use it to waste time” is “parents managing their children.”
  • Swift’s implementation of async/await looks great, and the stuff Apple’s doing with @MainActor seems to have met the goal of “the language eliminates an entire class of error” that initially gave us optionals, so I’m quite happy about that. I hope Vapor updates to async/await soon, I’d love to untangle some of my flatMap chains.
  • The new Human Interface Guidelines section on Inclusion is very well-written and hits on some important points.
  • I am incredibly excited about the virtual KVM thing coming to macOS and iPadOS – my desk setup features two monitors for macOS, and an iPad perpetually on the side, and the idea of being able to control the iPad without taking my hands off the main keyboard and mouse is very exciting.3
  • Safari on iOS got support for web extensions, which is neat. I also like that they’re pulling things down toward the bottom of the screen, should be more reachable.
  • Safari on macOS might finally do something that Mozilla never managed to do: convince me to switch to Firefox. I sincerely hope this is the peak of “aesthetics over functionality” in Apple’s design, and that it gets better from here, because this is a mess: lower contrast text, the core UI element moves around all the time so you can’t have any muscle memory, and they’ve buried every single button behind a ‘more’ menu.
  • SwiftUI got lots of little updates, but the thing that I may be most excited about is the simple quality of life improvements as a result of SE-299.listStyle(.insetGrouped) is much easier to type than .listStyle(InsetGroupedListStyle()), especially considering that Xcode can actually suggest .insetGrouped.
  1. In my case, it’ll probably be a couple days, and then Apple Music will punch through a Focus mode to notify me that Billie Eilish has a new single or something.
  2. I already have a name for the app I want to build using these API. My first feature would just be “the existing Screen Time system, but it actually knows how time works.” Something like 90% of the time that I hit ‘one more minute’ I’m instead given a couple seconds. Apple, I know that time is hard, but it isn’t that hard.
  3. Why the iPad? Because iPadOS is sandboxed, which limits the sort of bugs that Teams can introduce and evil that Zoom can. Sure, they both have macOS apps – but you shouldn’t install either of them.
Categories
Review

Circle of Magic

Tamora Pierce

I suspect I have mentioned in the past that Tamora Pierce’s Circle of Magic series is one of my favorite things to read. As a general rule, I reread most of her bibliography at least once a year – quite often when I’m stressed. Tamora Pierce is my comfort reading.1

I’ve recently stumbled my way into a lovely group of people, and among many of the projects we created for ourselves was something of a book club. And we started with the Circle of Magic.2

And here’s where I begin to struggle in writing this, nominally a review of the first quartet. I’ve been reading and rereading these books for so long that it’s impossible for me to come at them with fresh eyes. I can’t even begin to put myself in the mind of someone who hasn’t read them, to try to figure out what about them I should mention to convince someone they’re worth the time to read. I wouldn’t be the person I am today without their influence.

Instead, I’m just going to list some thoughts about the different books, in no particular order.

Tris’s Book is, I think, my favorite of the four. I identified the most with Tris, growing up – a total bookworm, and too quick a temper. It is, unsurprisingly, in Tris’s Book that we get to see what Tris is thinking, and what made her that person – and, conversely, that we see her start to grow, and learn to trust.

Briar’s Book scares me to this day. I’d say “even more so, considering,” but, having just reread it last week, I don’t think the amount it scares me has changed, even in light of living through a pandemic. It’s still terrifying – and it turns out to have been pretty accurate about just how scary, and lonely, and crushing it all is.

“Most disasters are fast, and big. You can see everyone else’s life got overturned when yours did. Houses are smashed, livestock’s dead. But plagues isolate people. They shut themselves inside while disease takes a life at a time, day after day. It adds up. Whole cities break under the load of what was lost. People stop trusting each other, because you don’t know who’s sick.”

Daja’s Book is all about the important of family, and how family doesn’t always match what you grew up thinking it would. You can find more people who love you, and who you love, and they can be just as much family as the one you were born into.

Daja’s Book is also… a big spoiler, in how I’m going to phrase this, so I’ll tuck it into a footnote. You’ve been warned!3

Sandry’s Book… is coming home.

I cannot, cannot express how much I love these books. Please, please give them a try.4 And, because I adore Tamora Pierce, also check out her patreon – the next goal is an admirable one.5

  1. In fact, many times the thing that makes me realize quite how stressed I am is the realization that I’ve picked up one of her novels. It’s automatic!
  2. The book club may have come about as a result of my strongly urging everyone to read these books. As far as the #influencer life goes, “encouraging people to read Tamora Pierce” is probably the best possible outcome.
  3. Daja’s Book is a beautiful example of a happy ending in a book, with everything getting tied together beautifully. It’s not just that every thread gets wrapped up nicely, it’s that half of them are solved by being the solution to another problem. To borrow a phrase that I first heard as a descriptor of another favorite piece of media of mine, it’s competence porn.
  4. I’m breaking my usual ‘use Bookshop links instead of Amazon’ pattern here, but Sandry’s Book isn’t available on Bookshop at all, and based on the paperback prices on Amazon, is thoroughly out of print. The Kindle edition is available, though!
  5. I’m of the opinion that she (or her staff) haven’t done a good enough job of advertising this, because I’ve had her public blog in my RSS reader for years, and just found out about the Patreon a few days ago. So now I’m doing my part by telling you, dear reader. Go support her! She’s great!
Categories
Programming

FocusedValue on tvOS

Alright, let’s set the scene with a mildly contrived example: you’ve got a list of things, and you want the surrounding View to match them. For a tvOS app, this could be swapping out the background and some title text to match the selected episode; since I don’t feel like coming up with some fake episode titles, however, we’re going to go with colors:

At first thought, you’d probably reach for @State, something akin to:

struct ColorsView: View { @State var selectedColor: Color let colors: [Color] var body: some View { VStack { Text(colorName(selectedColor)) HStack { ForEach(colors) { color in Rectangle().fill(color).focusable() } } } .background(WaveyShape().fill(selectedColor) } }
Code language: Swift (swift)

Not too bad; attach on onFocus to the Rectangle() and it should work!

But… what if there’s more layers in between? Instead of a Rectangle(), you’ve got some other View in there, maybe doing some other logic and stuff.

Oof, now we’re going to need a @Binding, and – oh, what happens if the user swipes out of the rectangles and to our nav bar, can selectedColor be nil?

Happily, SwiftUI has something built out to handle basically this exact scenario: @FocusedValue. There’s some great tutorials out there on how to do this for a macOS app, which allows you to wire up the menu bar to respond to your selection, but it works just as well on tvOS.

Let’s get started:

struct FocusedColorKey: FocusedValueKey { typealias Value = Color } extension FocusedValues { var color: FocusedColorKey.Value? { get { self[FocusedColorKey.self] } set { self[FocusedSeriesKey.self] = newValue } } }
Code language: Swift (swift)

Now we’ve got our new FocusedValue available, so let’s use it:

struct ColorsView: View { @FocusedValue(\.color) var selectedColor: Color? var body: some View { VStack { Text(colorName(selectedColor ?? Color.clear)) HStack { ForEach(colors) { ColorRectangleView(color: $0) } } } .background(WaveyShape().fill(selectedColor ?? Color.clear) } }
Code language: Swift (swift)

The one big change here is that selectedColor can be nil. I’ve gone ahead and defaulted to .clear, but do what fits your use case.

Finally, we need to set the focused item:

struct ColorRectangleView: View { let color: Color var body: some View { Rectangle() .fill(color) .focusable() .focusedValue(\.color, color) } } }
Code language: Swift (swift)

Et voila, it works!

Now, this may not seem like a huge change over doing it via @Binding, but keep in mind: @FocusedValue is a singleton. You can have every view in your app respond to this, without passing @Bindings every which way.

Categories
Playlist

Playlist of the Month: May 2021

If you haven’t gotten a vaccine yet, go do it.

Cologne – Haux on Something to Remember – EP

Slowly – ODIE on Slowly – Single

How It Was – Yoste on A Few Brief Moments – EP

Come On – Will Young on Echoes

We’ll Be Alright – Yoste on A Few Brief Moments – EP

DON’T TELL THE BOYS – Petey on Checkin’ Up on Buds – EP

Spaces – Jaymes Young on Spaces – Single

FOR THE REST OF MY LIFE – Zack Villere on FOR THE REST OF MY LIFE

Parachute (Piano Sessions) – Seafret on Parachute (Piano Sessions) – Single

Arcade – Duncan Laurence on Arcade – Single

twentyfive – Yoste on twentyfive – Single

Lightning – Hayden Calnin on Lightning – Single

Oh Dear, Oh Beaux – beaux on A Love Letter To the Moments Spent Outside

skip.that.party – X Ambassadors & Jensen McRae on skip.that.party – Single

I’m God – Clams Casino & Imogen Heap on I’m God – Single

Lost In the World (feat. Bon Iver) – Kanye West on My Beautiful Dark Twisted Fantasy

I Can’t Lose You – Isak Danielson on Tomorrow Never Came

Broken Heart Gang – Teflon Sega on Broken Heart Gang – Single

Losing My Religion – Shawn James on The Lake Wenatchee Sessions – EP

MONTERO (Call Me By Your Name) – Lil Nas X on MONTERO (Call Me By Your Name) – Single

Science/Visions – CHVRCHES on In Search of Darkness – EP

House of Balloons / Glass Table Girls (Original) – The Weeknd on House of Balloons (Original)

On My Own (feat. Kid Cudi) – Jaden on ERYS

Neon Medusa – The Midnight on Horror Show – EP

We Don’t Talk Enough – Quinn XCII & Alexander 23 on Change of Scenery II

We Got Gods to Blame – Hayden Calnin on We Got Gods to Blame – Single

Flow – Vide on Flow – Single

Into Your Arms – Vide on Into Your Arms – Single

Starlite – HOKO on Heathen

DIM – SYML on DIM – EP

Blinding Lights – X Ambassadors on Blinding Lights – Single

Raining – daydream Masi on Movie Scenes EP

BLACK TEETH – SYML on DIM – EP1

Good Graces – Spencer William on Little Wars – EP

Only Pieces of the Truth – Jordan Hart on Only Pieces of the Truth

All the Lights – By The Coast on All the Lights – Single

I Hate You For This – Munn on I Hate You For This – Single

CRYSTAL BALL – Jake Miller on CRYSTAL BALL – Single

Magic – Phillip LaRue on Night Swimming – Single

I Took a Pill in Ibiza, Youth (Acoustic Mashup) [feat. Chasing Taylor] – Landon Austin on Covers: Six

Overtime – Griffin Stoller on Overtime – Single

From the Back of a Cab – Rostam on Changephobia2

Always – Isak Danielson on Yours

So Handsome Hello – Woodkid on Powerplant – EP

Young Blood – PHOEBE ∆X∆ on Wisdom Teeth, Pt. 1 – EP

Astronaut In The Ocean – Masked Wolf on Astronaut In The Ocean – Single

Rät – Penelope Scott on Public Void3

Peach Scone – Hobo Johnson on Peach Scone – Single4

Fashion – Emilee Moore on Fashion – Single

Golden Days – ferdinant. on Golden Days – Single

Follow – Vide on Follow – Single

Empty Space – Teflon Sega on Empty Space – Single

Because the Night (feat. Nikki Flores) – The Midnight on Horror Show – EP

Good in Red – The Midnight on Horror Show – EP5

Klan – Mahmood & DRD on Klan – Single

Somewhere We Can Be Alone – Gunnar Gehl on Somewhere We Can Be Alone – Single

Symphony – Forester on Symphony – Single

The Stranger – The Midnight on Horror Show – EP

Shadows in the Dark (feat. Elliot Moss) – HNTR on Shadows in the Dark (feat. Elliot Moss) – Single

Devil Make a Deal – The Midnight on Horror Show – EP

Ghost in Your Stereo – The Midnight on Horror Show – EP

Hallucinogenics (Vallis Alps Remix) – Matt Maeson on The Remixes – EP

can’t give back your love – Clide on can’t give back your love – Single

Stay a Little Longer (feat. Vide) – LANDR & Nander on Stay a Little Longer (feat. Vide) – Single

Bite Marks – Au/Ra on Bite Marks – Single

If I Cared – Jack in Water on If I Cared – Single

start//end – EDEN on vertigo

Coldplay (feat. Vic Mensa) – Mr Hudson on Coldplay (feat. Vic Mensa) – Single

Save Me – Majik on It’s Alright / Save Me – Single6

  1. “You were an honest mistake.” oof.
  2. I also quite like the music video, it’s a nice counterpart to the song.
  3. “It tasted like Thomas Malthus.” is a truly excellent line. Really, all the lyrics in here are great.
  4. This whole thing feels like he just hit record and did the entire vocal track in one go, and then filled in the music afterwards. Very improvisational feel.
  5. Really enjoying this whole EP, in case you can’t tell by the way I’ve got the entire thing on this playlist.
  6. I miss Majik so much, and I hope the guys that used to be the band are doing well.
Categories
Programming

Position vs Offset

I’ve had reason recently to be doing custom position of things in SwiftUI, and figured I’d share something that I found a bit of a tricky distinction at first: position vs offset.

So, let’s set the scene:

struct DemoView: View { var body: some View { HStack { Rectangle().fill(.gray) Rectangle().fill(.blue) Rectangle().fill(.gray) } } }
Code language: Swift (swift)

And now I’ll add some visuals, for ease of reading. (I made these in Sketch, so the graphics aren’t precisely what you’d get by running this code, but hey, artistic liberties.) Here’s our view:

Three squares in a row.

Now let’s tinker!

struct DemoView: View { var body: some View { HStack { Rectangle().fill(.gray) Rectangle().fill(.blue) .offset(x: 150, y:150) Rectangle().fill(.gray) } } }
Code language: Swift (swift)

We’re offsetting the middle square. What does that look like?

Three squares; two are in a row, and there is a space in the middle where the third could fit, but it is below and to the right of that space.

I’ve left in a little ghost image to show where it was, because it’s an important distinction! As far as the HStack is concerned, that space is still occupied. Think of it like throwing your voice – you don’t move, just the perception of where you are.

Let’s try something else, now:

struct DemoView: View { var body: some View { HStack { Rectangle().fill(.gray) Rectangle().fill(.blue) .position(x: 150, y:150) Rectangle().fill(.gray) } } }
Code language: Swift (swift)

Looks pretty similar in code, right? We’ve just swapped out ‘offset’ for ‘position’. What do we get on screen?

Three squares; two are in a row, while the third is out of alignment and slightly overlapping.

Ooh, very different! No more ghost, because now it’s actually in a different place – not holding that spot in the HStack. It’s also in a different spot than the previous one, what gives?

It’s in the name: ‘offset’ offsets the view from where it normally would’ve been. Our starting position was where the ghost stayed:

Three squares; two are in a row, and there is a space in the middle where the third could fit, but it is below and to the right of that space. The distance from the space to the square is labeled, with 150 in the top distance and 150 in the left distance.

‘Position,’ on the other hand, skips the whole question of where it would go and instead just puts it in an exact spot, using the top left corner of the screen as the point (0,0):

Three squares; two are in a row, while the third is out of alignment and slightly overlapping. The distance from the misaligned square to the top and left of the image are labeled with '150' on each.

The other approach that worked for my brain, coming from doing a lot of web dev, is to think about ‘offset’ as being CSS’ ‘position: relative’, while ‘position’ is equivalent to ‘position: absolute’.

Hopefully this helps the whole thing make sense!

Categories
Programming

Transitions in ZStacks

The other day, I found a fun little SwiftUI edge case: using a .transition() modifier in a ZStack doesn’t work. As a basic demo, let’s do something like this:

struct DemoView: View { @State var showing = true var body: some View { ZStack { Rectangle().fill(Color.red) .onTap { showing.toggle() } if (showing) { Text("Hello, world!") .transition(.opacity) } } } }
Code language: Swift (swift)

Pretty simple example, yeah? Seems like it’d Just Work(TM) out of the box. It doesn’t, though; instead of a lovely opacity animation, it just pops in and out. Hmm.

Worry not, though, as I have a fix:

struct DemoView: View { @State var showing = true var body: some View { ZStack { Rectangle().fill(Color.red) .onTap { showing.toggle() } .zIndex(1) if (showing) { Text("Hello, world!") .transition(.opacity) .zIndex(2) } } } }
Code language: Swift (swift)

Et voila, your transition works!

Now, I haven’t exactly done rigorous experimentation to figure out why, exactly, this works, but I do have a theory: when SwiftUI removes the Text, it loses its place in the ZStack, and gets assigned to “behind everything else.” By manually specifying the order, the slot stays reserved, and it can stay there while it fades in and out.

Categories
Programming

Playing Videos in SwiftUI

As of WWDC 2020, we have a way to play videos in SwiftUI without bailing out to UIKit with UIViewRepresentable. At first glance, it’s pretty simple, as well:

import SwiftUI import AVKit struct VideoPlayerView: View { let videoURL: URL var body: some View { let player = AVPlayer(url: videoURL) VideoPlayer(player: player) } }
Code language: Swift (swift)

Et voila, you’re playing a video! You can overlay content on top of the video player pretty easily:

import SwiftUI import AVKit struct VideoPlayerView: View { let videoURL: URL var body: some View { let player = AVPlayer(url: videoURL) VideoPlayer(player: player) { Text("Watermark") } } }
Code language: Swift (swift)

Seems like we’re good to go, no?

Well, not quite. Let’s talk memory management.

VideoPlayerView is a struct – it’s immutable. SwiftUI allows us to mutate the state of our views with user interaction using things like @State, thanks to some Compiler Magic.

Every time some aspect of the state changes, SwiftUI calls the body getter again.

Spotted the catch yet?

We’re declaring the AVPlayer instance inside the body getter. That means it gets reinitalized every time body gets called. Not the best for something that’s streaming a video file over a network.

But wait, we’ve already mentioned the Compiler Magic we can use to persist state: @State! Let’s try:

import SwiftUI import AVKit struct VideoPlayerView: View { let videoURL: URL @State var player = AVPlayer(url: videoURL) var body: some View { VideoPlayer(player: player) } }
Code language: Swift (swift)

Whoops. We’ve got a problem – self isn’t available during initialization, so we can’t initialize the AVPlayer like that. Alright, we’ll write our own init:

import SwiftUI import AVKit struct VideoPlayerView: View { let videoURL: URL @State var player: AVPlayer init(videoURL: URL) { self.videoURL = videoURL self._player = State(initialValue: AVPlayer(url: videoURL)) } var body: some View { VideoPlayer(player: player) } }
Code language: Swift (swift)

(I suppose we could drop the let videoURL: URL there, since we’re using it immediately instead of needing to store it, but for consistency’s sake I’m leaving it in.)

Okay, sounds good – except, hang on, @State is only intended for use with structs, and if we peek at AVPlayer it’s a class.

Okay, no worries, that’s what @StateObject is for, one more tweak:

import SwiftUI import AVKit struct VideoPlayerView: View { let videoURL: URL @StateObject var player: AVPlayer init(videoURL: URL) { self.videoURL = videoURL self._player = StateObject(wrappedValue: AVPlayer(url: videoURL)) } var body: some View { VideoPlayer(player: player) } }
Code language: Swift (swift)

There, we should be good to go now, right? Right?

Alas, the compiler says no. AVPlayer doesn’t conform to ObservableObject, so we’re out of luck.

Fortunately, ObservableObject is pretty easy to conform to, and we can make our own wrapper.

import SwiftUI import AVKit import Combine class PlayerHolder: ObservableObject { let player: AVPlayer init(videoURL: URL) { player = AVPlayer(url: videoURL) } } struct VideoPlayerView: View { let videoURL: URL @StateObject var playerHolder: PlayerHolder init(videoURL: URL) { self.videoURL = videoURL self._player = StateObject(wrappedValue: PlayerHolder(videoURL: videoURL)) } var body: some View { VideoPlayer(player: playerHolder.player) } }
Code language: Swift (swift)

Phew. At long last, we’ve got a stable way to hold onto a single AVPlayer instance. And, as a bonus, we can do stuff with that reference:

import SwiftUI import AVKit import Combine class PlayerHolder: ObservableObject { let player: AVPlayer init(videoURL: URL) { player = AVPlayer(url: videoURL) } } struct VideoPlayerView: View { let videoURL: URL @StateObject var playerHolder: PlayerHolder init(videoURL: URL) { self.videoURL = videoURL self._player = StateObject(wrappedValue: PlayerHolder(videoURL: videoURL)) } var body: some View { VideoPlayer(player: playerHolder.player) .onAppear { playerHolder.player.play() } } }
Code language: Swift (swift)

Will start playing the video as soon as the view opens. Similarly, you could add some Buttons, build your own UI on top of the video player, all sorts of fun.

And, from the other end, you can put more logic in the PlayerHolder, as well. Say you need some additional logic to get from a video ID to the actual URL that the AVPlayer can handle? Try something like this:

class PlayerHolder: ObservableObject { @Published var player: AVPlayer? = nil init(videoID: Video.ID) { NetworkLayer.shared.lookupVideoInformation(videoID) { result in self.player = AVPlayer(url: result.url) } } }
Code language: Swift (swift)

(Now, that’s not the nice, Combine-y way to do it, but I’ll leave that as an exercise for the reader. Or possibly another post.)

Categories
Programming

tvOS Carousels in SwiftUI

It’s a fairly common pattern in tvOS apps to have a carousel of items that scrolls off screen in either direction – something vaguely like this:

Image a tvOS wireframe, showing two horizontally-scrolling carousels.

Actually implementing this in SwiftUI seems like it’d be easy to do at first:

VStack { Text("Section Title") ScrollView(.horizontal) { HStack { ForEach(items) { ItemCell(item: $0) } } } }
Code language: Swift (swift)

Which gets you… a reasonable amount of the way there, but misses something: ScrollView clips the contents, and you wind up looking like this:

A tvOS wireframe, showing two horizontally-scrolling carousels; both have been clipped to the wrong visual size.

Not ideal. So, what’s the fix? Padding! Padding, and ignoring safe areas.

VStack { Text("Section Title").padding(.horizontal, 64) ScrollView(.horizontal) { HStack { ForEach(items) { ItemCell(item: $0) } } .padding(64) // allows space for 'hover' effect .padding(.horizontal, 128) } .padding(-64) } .edgesIgnoringSafeArea(.horizontal)
Code language: Swift (swift)

The edgesIgnoringSafeArea allows the ScrollView to expand out to the actual edges of the screen, instead of staying within the (generous) safe areas of tvOS.1

That done, we put the horizontal padding back in on the contents themselves, so that land roughly where we want them. (I’m using 128 as a guess; your numbers may vary, based on the design spec; if you want it to look like The Default, you can read pull the safe area insets off UIWindow.)

Finally, we balance padding on the HStack with negative padding on the ScrollView; this provides enough space for the ‘lift’ (and drop shadow, if you’re using it) within the ScrollView, while keeping everything at the same visual size.

  1. tvOS has large safe areas because TVs are a mess in regards to useable screen area.
Categories
Playlist

Playlist of the Month: April 2021

Very nearly didn’t get this written up on time; I had a rough time of it with the vaccine side effects, but I’m thankfully through it now. And vaccinated! Get a vaccine, folks – a day of feeling like crap is better than getting covid, and better than giving someone else covid.

Cologne – Haux on Something to Remember – EP

Somewhere (feat. Octavian) – The Blaze on Somewhere (feat. Octavian) – Single

Slowly – ODIE on Slowly – Single

How It Was – Yoste on A Few Brief Moments – EP

Come On – Will Young on Echoes

We’ll Be Alright – Yoste on A Few Brief Moments – EP

DON’T TELL THE BOYS – Petey on Checkin’ Up on Buds – EP

Spaces – Jaymes Young on Spaces – Single

FOR THE REST OF MY LIFE – Zack Villere on FOR THE REST OF MY LIFE

Nuke the Moon – Epic Mountain on Nuke the Moon – Single

Crash Into Me – Petey on Crash Into Me – Single

The Light – Richard Walters on The Light – Single

Parachute (Piano Sessions) – Seafret on Parachute (Piano Sessions) – Single

Lo Vas A Olvidar – Billie Eilish & ROSALÍA on Lo Vas A Olvidar – Single

Cold Sets In – World’s First Cinema on Cold Sets In – Single

Arcade – Duncan Laurence on Arcade – Single1

I’m Worried About You – Addict., REWiND & Achex on I’m Worried About You – Single

Red Run Cold – World’s First Cinema on Red Run Cold – Single

twentyfive – Yoste on twentyfive – Single

emily – Jeremy Zucker & Chelsea Cutler on brent ii – EP

fan behavior – Isaac Dunbar on evil twin

Lightning – Hayden Calnin on Lightning – Single

Oh Dear, Oh Beaux – beaux on A Love Letter To the Moments Spent Outside

skip.that.party – X Ambassadors & Jensen McRae on skip.that.party – Single

im a bad friend – Andy H on im a bad friend – Single

Night Like This – daydream Masi on Movie Scenes EP

Hands On The Devil – Smeyeul. & Galvanic on Hands On The Devil – Single

Calamity Song – The Decemberists on The King Is Dead

Whatever You Like – Anya Marina on Whatever You Like [Digital 45]

Hide & Seek (Imogen Heap Cover) – Amber Run on **

I’m God – Clams Casino & Imogen Heap on I’m God – Single

Lost In the World (feat. Bon Iver) – Kanye West on My Beautiful Dark Twisted Fantasy2

I Can’t Lose You – Isak Danielson on Tomorrow Never Came

There for You – Daniel Allan on There for You – Single

RLNDT – Bad Bunny on X 100PRE

Rohypnol (feat. Fixway & Samer) – APRE on 19

Video Games – Black Match on Basement Covers I – Single

Blindside – Nathan Ball on Blindside – Single

Float On – Phil Good on Float On – Single

Broken Heart Gang – Teflon Sega on Broken Heart Gang – Single

Losing My Religion – Shawn James on The Lake Wenatchee Sessions – EP

Wicked Game (feat. Cal Trask) – StayLoose on Wicked Game (feat. Cal Trask) – Single

Wicked Games (Original) – The Weeknd on House of Balloons (Original)3

Can I Be The One? – daydream Masi on Movie Scenes EP

Death Stranding – CHVRCHES on In Search of Darkness – EP

Long Distance – JORDY on Long Distance – EP

Empty Eyes – Munn on Empty Eyes – Single

As I Am (feat. Khalid) – Justin Bieber on Justice (Triple Chucks Deluxe / Deluxe Video Version)

Human – Peter Thomas on Let It All Happen

MONTERO (Call Me By Your Name) – Lil Nas X on MONTERO (Call Me By Your Name) – Single4

Resolve – Hayden Everett on Resolve – Single

Tether – CHVRCHES on In Search of Darkness – EP

Science/Visions – CHVRCHES on In Search of Darkness – EP

carpool – Zachary Knowles on carpool – Single

House of Balloons / Glass Table Girls (Original) – The Weeknd on House of Balloons (Original)

It’s Raining, It’s Pouring – Anson Seabra on It’s Raining, It’s Pouring – Single

BERNADETTE (feat. Joyce Wrice) – Zack Villere on BERNADETTE (feat. Joyce Wrice) – Single

On My Own (feat. Kid Cudi) – Jaden on ERYS5

Feeling Whitney – Post Malone on Stoney (Deluxe)

11H30 – Danger on 09/14 2007 – EP

Broken Bones – CHVRCHES on In Search of Darkness – EP

Peaches (feat. Daniel Caesar & GIVĒON) – Justin Bieber on Justice (Triple Chucks Deluxe / Deluxe Video Version)

Addicted – Blakey on Addicted – Single

Just Fine – Joel Ansett on Just Fine – Single

Destroyer – Of Monsters and Men on Destroyer – Single6

22Hrs – Teflon Sega & OZZIE on 22Hrs – Single7

Vincent Price – HCK9 on Vincent Price – Single

Hollow – Jon Bryant on Hollow – Single

Neon Medusa – The Midnight on Horror Show – EP8

Holding Back (feat. Benjamin Yellowitz) – DYVR on Holding Back (feat. Benjamin Yellowitz) – Single

We Don’t Talk Enough – Quinn XCII & Alexander 23 on Change of Scenery II

Change For the Better – Star Seed & Suave on Change For the Better – Single

9 Crimes – Damien Rice on 9 [Explicit]

Ride It – Regard on Ride It – Single

We Got Gods to Blame – Hayden Calnin on We Got Gods to Blame – Single

Schemers – Steve Benjamins on Schemers – Single

Flow – Vide on Flow – Single

Can You Hear Me? – Munn on Can You Hear Me? – Single

Into Your Arms – Vide on Into Your Arms – Single

Lies – Sloane & Cobe Jones on Lies – Single

Envidioso – Ozuna & Ovi on Envidioso – Single

Starlite – HOKO on Heathen9

Only Human – Woodlock on The Future of an End

Anxious Smothers – Jack in Water on Anxious Smothers – Single

Devil Knows – Armen Paul on Devil Knows – Single

ADDERALL – Jake Miller on ADDERALL – Single

DIM – SYML on DIM – EP

Blinding Lights – X Ambassadors on Blinding Lights – Single10

Raining – daydream Masi on Movie Scenes EP

BLACK TEETH – SYML on DIM – EP

Good Graces – Spencer William on Little Wars – EP

Something Just Like This – The Chainsmokers & Coldplay on Something Just Like This

  1. Really fun to sing along to, but I keep winding up in the wrong key if I try to sing it without the music. Not sure what’s going on there.
  2. One day I want to arrange an a cappella cover of this, because it just seems like it’d be a fun challenge.
  3. Still amused by the coincidence of these two being in a row.
  4. If you haven’t watched the video yet, you really should. It’s great.
  5. Hey, that new Spider-Man game is fun! Really suffers from the very-overdone “Spider-Man can’t let his loved ones find out he’s Spider-Man” thing, but still, fun gameplay.
  6. Apparently “Of Monsters and Men” is my Moving Soundtrack?
  7. Realized as I was putting this playlist together that I’ve been misreading the artist as “Teflon Saga” this whole time.
  8. Very ‘80s vibes to this one, it’s quite fun.
  9. Favorite addition this month.
  10. A nice acoustic kind of cover.