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)
			}
		}
	}
}

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)
			}
		}
	}
}

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)
	}
}

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")
		}
	}
}

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)
	}
}

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)
	}
}

(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)
	}
}

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)
	}
}

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()
			}
	}
}

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)
		}
	}
}

(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)
			}
		}
	}
}

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)

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.
Categories
Review

“How to Avoid a Climate Disaster”

Bill Gates

I just have to begin by expressing my admiration for Bill Gates. Which still feels strange – I grew up on “Micro$oft” jokes and the image of Gates as the corporate Big Brother, a la Apple’s 1984 ad. Watching him go from icon of capitalism to the world’s foremost philanthropist has been interesting, to say the least. As a relevant aside, I highly recommend the Netflix documentary on his life, it’s fascinating, and works well to provide context on where he’s coming from in writing this book.

The book itself does what it says on the tin: it ends with plans of action for preventing the sort of global climate disaster that we, as a species, have been gleefully sprinting towards ever since we realized those funky rocks we dug up would burn longer than the trees we were chopping down. And the plans aren’t just “buy an electric car and vote for green energy;” not only are there more action items than just that, there are plans for people depending on which hat they’re wearing. Sure, you the consumer can buy an electric car… but you the citizen can write your legislators, and you the employer can invest in R&D, and you the local government official can tweak building codes to allow for more efficient materials.

The first half, or more, of the book is an accounting of what’s driving climate change, and it’s a fascinating overview. Your first guess about the largest culprit, in broad categories, is probably wrong.

And in the middle, there’s a great deal of discussion of the technologies we’re going to need to get through this transition. As a life-long nerd, that was the part I enjoyed the most; as someone who’s very sold on the importance and utility of nuclear power, my absolute favorite moment was a throwaway reference to “we should be building nuclear-powered container ships.”1

Here at the end, where I usually say “I enjoyed this book, and I recommend it,” I’m still going to do that.2 But beyond enjoying the book, it feels like the single most important thing I’ve read… possibly ever. The pandemic is the definitive crisis of the last couple years; climate change is the definite crisis of this generation. Go read the book. Buy a copy, read it, and pass it along to someone else to read. Take notes, and follow the plans of action that’re applicable to you. Let’s go save the world.

  1. I may have set some kind of land-speed record going from “what the hell” to “that makes perfect sense.”
  2. It’s not that I like every book I read, it’s that, as a general rule, I don’t write reviews of the ones I don’t like. If you don’t have anything nice to say, don’t say anything at all.
Categories
Technology

Custom Queries in Vapor Fluent

While the QueryBuilder interface is pretty neat, it’s still missing some things. Recently, I needed a GROUP BY clause in something, and was rather unsurprised to find that Fluent doesn’t support it.1

Fortunately, it’s still possible to write custom SQL and read in the results. Make yourself a convenience struct to unpack the results:

struct MyQueryResult: Codable {
	let parentID: Parent.IDValue
	let sum: Double
}

(Strictly speaking, it can be Decodable instead of Codable, but as long as the Parent.IDValue (generated for free by making Parent conform to Model, I believe) is Codable, Swift generates the conformance for us.)

Now, in your controller, import SQLKit, and then get your database instance as an SQL database instance:

guard let sqlDatabase = req.db as? SQLDatabase else { 
	// throw or return something here
}

After that, write your request:

let requestString = "SELECT ParentID, SUM(Value) FROM child GROUP BY ParentID"

Note – your syntax may vary; I found that, using Postgres, you need to wrap column names in quotes, so I used a neat Swift feature to make that less painful:

let requestString = #"SELECT "ParentID", SUM("Value") FROM child GROUP BY "ParentID""#

If you want to use string interpolation, swap out \() for \#().

Finally, make the query:

return sqlDatabase.raw(SQLQueryString(requestString)).all(decoding: MyQueryResult.self)
  1. Entity Framework Core, which is an incredibly robust, full-featured ORM, only barely supports GROUP BY, so seeing this rather young ORM not support it isn’t all that shocking.
Categories
Development

Forms & Lists

If there’s one area where SwiftUI really shines, it’s forms and lists. I have one area of the app that’s meant as, at most, a fallback option for managing some data, and putting together that management interface took, oh, an hour? It was a breeze. Admittedly, it’s not the prettiest list I’ve ever made, but like I said: fallback option.

Screenshot of a list with a title, two items showing a date and value each, and an 'add' button.
Managing data points in a data set. Unpolished? Sure. Functional? Absolutely.
Screenshot of a Settings screen showing two lists, with an 'edit' button.

I was delighted to find that the automagic ‘edit’ button function handles multiple editable lists within the same View. As this is part of the ‘Settings’ screen, one of the three core screens in the app, it has received a bit more polish.

And I’ve continued to have fun building custom versions of the Picker control, with an expansion on my previous custom picker to support inline management of the above Data Sets and the addition of another one for picking a type of graph:

Screenshot of a picker showing three types of graphs.

At the moment, I’m showing a pretty basic dataset for these, but at some point I think I may create something a bit more visually interesting. The trick being, of course, that I can’t just random-number-generate the data, because I want all three to show the same data points, and since the user can also control the color, I want it to stay consistent if you leave, change the color, and come back.

(The solution here is probably to hard-code a data set, but where’s the fun in that?)

Categories
Development

File > New > Project…

I recently started working on a new development project. Or rather, a project I’ve been thinking about for a while, but just recently started developing – the first draft of the design is from almost a year ago, now, something that I worked on as a class project. But, mostly on a whim, I signed up for SwiftUI Jam, and took it as an excuse to start actually building the thing.

Now, normally my approach to projects is very Apple – refuse to admit I’m even working on something new until it’s complete, ready to present to the world. This time, though, the vague rules of the jam meant it had to be done at least somewhat in the open, and I figured I may as well do some proper write-ups as I go. Could be interesting.

I’m starting with… not the first thing I built, but the one that was the most fun so far. I’m doing the whole app in SwiftUI, and it really shines for building forms.

Screenshot of an iOS application showing a form: Title, Color, and Data Set; Comparison? is set to false.
Screenshot of an iOS application showing a form: Title, Color, and Data Set; Comparison? is set to true. The first three questions are repeated.
Screenshot of an iOS application showing a list to choose from, with groupings such as Activity featuring items like Calories Burned and Cycling Distance.

As I said, a fairly simple form – Title, Color, and Data Set, with the option to add a second of the same three items. The Data Set picker is a custom version of a Picker, because I wanted to give the options in a grouped list rather than just alphabetical order.

I suspect I’m going to be building a second custom Picker implementation sometime soon – this time, choosing from more visual options. Should be fun to put together.

Categories
Playlist

Playlist of the Month: March 2021

This has been the kind of month that managed to go by in the blink of an eye, and also lasted approximately a year and a half.

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

Call Him – Noah Cunane on Call Him – Single

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 LIFE1

Get My Fix – Adam Oh on Get My Fix – Single

Anticipation – Steve Benjamins on Anticipation – Single

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

Falling Fire – Forester on A Range of Light

One Last, Last Time – Armen Paul on One Last, Last Time – 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

SHE – Winona Oak on SHE – EP

Night Drives – Devan on Pink Noise – EP

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

Drowning – Armen Paul on Drowning – Single

Time to Sink – Edwin Raphael on Time to Sink – Single

Arcade – Duncan Laurence on Arcade – Single

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

Boy on the Moon – GROUNDSTROEM on Boy on the Moon – EP

cheers – blackbear & Wiz Khalifa on cheers – Single

Loves You Like I Couldn’t Do – Duncan Laurence on Small Town Boy

Echo – Blakey on Echo – Single

You Don’t Look at Me the Same – Great Good Fine Ok & Yoke Lore on You Don’t Look at Me the Same – Single

Ricochet – Griffin Stoller on Ricochet – Single

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

All Too Well – Jake Scott on All Too Well

At Night – Charles Fauna on At Night – Single

fan behavior – Isaac Dunbar on evil twin

(They Long To Be) Close To You – AJIMAL on (They Long To Be) Close To You – Single2

Lightning – Hayden Calnin on Lightning – Single

100 Miles – Steve Benjamins on 100 Miles – Single

Trust Issues – Spencer William on Trust Issues – Single

Wicked Game – Cal Trask on Wicked Game – Single

4Runner – Rostam on Changephobia

These Kids We Knew – Rostam on Changephobia

Keeping Your Head Up – JacobNeverhill & Nora Bart on Live at Home – EP

KING – LANKS & Yorke on SPIRITS PT.2

Mild Sanity (feat. Juletta) – Edwin Raphael on Staring at Ceilings – EP

Haircut – Petey & Miya Folick on Haircut – Single

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

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

BOY WITH A BROKEN HEART – Noah Cunane, Lonr & Jutes on BOY WITH A BROKEN HEART – Single

Hate You + Love You (feat. AJ Mitchell) – Cheat Codes on Hate You + Love You (feat. AJ Mitchell) – Single

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

Night Like This – daydream Masi on Movie Scenes EP

Flustered Snowflakes – Pilar Victoria on Hi, My Name Is Pily! – EP

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

Dead Girl! – Au/Ra on Dead Girl! – Single

The Worst – Spencer William on The Worst – Single

Feel It Coming – Woodlock on The Future of an End

(Title Redacted – Spoilers!) – Kristen Anderson-Lopez & Robert Lopez on WandaVision: Episode (Redacted) (Original Soundtrack)4

Nobody Loves Me (feat. ELIO) – Winona Oak on Nobody Loves Me (feat. ELIO) – Single

Te Olvidaste – C. Tangana & Omar Apollo on El Madrileño5

If I Didn’t Have You – BANNERS on If I Didn’t Have You – Single

Here We, Here We, Here We Go Forever – Mogwai on As the Love Continues6

Pompeii (Kat Krazy Remix) – Bastille on Remixed

Caves – Haux on All We’ve Known – EP

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**

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

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

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

RLNDT – Bad Bunny on X 100PRE

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

Colours – Goth Babe on Smith Rock – Single

Video Games – Black Match on Basement Covers I – Single

Make Up Your Mind – Devan on Pink Noise – EP

Freedom – Jordan Hart on Only Pieces of the Truth

I’m Not Going Back – Kina & Mokita on I’m Not Going Back – Single

Blindside – Nathan Ball on Blindside – Single

Float On – Phil Good on Float On – Single8

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

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

  1. Between this and DON’T TELL THE BOYS I’ve apparently stumbled across subgenre of all-caps… whatever these two are grouped as. They go together, I just can’t think of a name for the group.
  2. Fun fact: every time this plays, I briefly get “come with me… and you’ll be… in a world of pure imagination” stuck in my head.
  3. Top pick this month!
  4. If you’ve seen it, you know what song this is. If you haven’t, don’t click! It’ll be much more fun that way.
  5. This is why RLNDT is back on the list, a bit further down – reminded me of it.
  6. This whole album is very good, but I’ve found that I have to be in the mood for Mogwai.
  7. This little Imogen Heap section brought to you by this video.
  8. This is a really excellent cover because it’s completely different from the original.
  9. Shawn James is just a whole mood sometimes, y’know?
Categories
Technology

Serving ‘files’ in Vapor

In my experience, dynamically generating a file, serving it immediately, and not persisting it on the server is a pretty common use case. In general, this is one of two things – either a PDF download, or a CSV. While my Vapor tinkering hasn’t yet given me an opportunity to generate PDFs on the server, I have had an occasion to create a CSV, and wrote up a little helper for doing so.

import Vapor

struct TextFileResponse {
    enum ResponseType {
        case inline, attachment(filename: String)
    }
    
    var body: String
    var type: ResponseType
    var contentType: String
}

extension TextFileResponse: ResponseEncodable {
    public func encodeResponse(for request: Request) -> EventLoopFuture<Response> {
        var headers = HTTPHeaders()
        headers.add(name: .contentType, value: contentType)
        switch type {
        case .inline:
            headers.add(name: .contentDisposition, value: "inline")
        case .attachment(let filename):
            headers.add(name: .contentDisposition, value: "attachment; filename=\"\(filename)\"")
        }
        return request.eventLoop.makeSucceededFuture(.init(status: .ok, headers: headers, body: .init(string: body)))
    }
}

That’ll work for any file you can assemble as text; CSV just struck me as being the most useful example. Use ResponseType.inline for a file you want displayed in a browser tab, and .attachment if it’s for downloading.

And if you’re doing a lot of CSVs, give yourself a nice little helper:

extension TextFileResponse {
    static func csv(body: String, name: String) -> TextFileResponse {
        .init(body: body, type: .attachment(filename: name), contentType: "text/csv")
    }
}
Categories
Technology

“All organizational systems fall on a spectrum from Calendar to To-Do List”

Something I said to a coworker recently. Largely inspired by listening to Cortex, and I felt like giving it a slightly more visual treatment.

Categories
Technology

Default Values in Vapor Fluent

My recent tinkering has been with Vapor, and while I mostly like their Fluent ORM, it has some rough edges and semi-undocumented behavior. At some point, I’ll feel confident enough in what I’ve learned through trial and error (combined with reading the source code – open source!) to actually make some contributions to the documentation, but for now, I’m going to throw some of the things I struggled with up here.

If you’re using a migration to add a column, and specifically want it to be non-null, you’ll need a default value. My first approach was to do a three-step migration, adding the column as nullable, then filling the default value on all rows, and then setting the column to be non-null, but that didn’t feel right. Eventually, though, I figured out how to express a DEFAULT constraint in Fluent:

let defaultValueConstraint = SQLColumnConstraintAlgorithm.default(/* your default value here */)

Then, in your actual schema builder call:

.field("column_name", /* your type */, .sql(defaultValueConstraint), .required)

Note that SQLColumnConstraintAlgorithm isn’t available from the Fluent module, you’ll need to import SQLKit first.

And here, a full worked example:

import Vapor
import Fluent
import SQLKit

struct DemoMigration: Migration {
    func prepare(on database: Database) -> EventLoopFuture<Void> {
        let defaultValueConstraint = SQLColumnConstraintAlgorithm.default(false)
        return database.schema(DemoModel.schema)
            .field(DemoModel.FieldKeys.hasBeenTouched, .bool, .sql(defaultValueConstraint), .required)
            .update()
    }
    
    func revert(on database: Database) -> EventLoopFuture<Void> {
        database.schema(DemoModel.schema)
            .deleteField(DemoModel.FieldKeys.hasBeenTouched)
            .update()
    }
}

(For context, I’m in the habit of having a static var schema: String { "demo_model" } and a struct FieldKeys { static var hasBeenTouched: FieldKey { "has_been_touched" } } within each of my Fluent models – it keeps everything nice and organized, and avoids having stringly-typed issues all over the place.)

Categories
Review

“The Last Thing He Wanted”

Joan Didion

This book took a while to really capture my attention, in terms of time. In terms of how far into the book it took, I suspect it was about the usual amount of time it takes a book to grab me. The distinction being, usually I read books like I’ve got a grudge, like I’m trying to see how fast I can cram all these words into my brain. Not so, with this one — I’d read a chapter or two, and put it down. Sometimes for a couple minutes, so I could sit and process a bit, and then pick it up and continue; other times, it’d be a day or two before I tried again.

All in all, this isn’t the kind of book I tend to go for. It feels much more Literary than my default — which is largely the writing style, but something about the paper and the typesetting makes it feel like the kind of book I’d read for English class in high school, filling it with notes and highlights and a ridiculous amount of sticky notes.

By the end, it feels… semi-coherent. Which, by then, you’ve grown used to, because at the beginning it’s entirely incoherent. The writing style is “first draft of a book by somebody who got a doctorate on a specific week of history and has no grasp of the concept of expert blind spot.”

At the end, though, I liked the book. Apparently it’s been developed into a Netflix film, the cover tells me; I may watch it, because I can’t imagine the film adaptation at all feeling like the book.

In writing this, I can also tell just how much Didion’s writing style has influenced mine, at least at the moment. Consider this a cheap knock-off of a demo. And then go read the real thing, instead.

Categories
Playlist

Playlist of the Month: February 2021

I’ve had a very eventful month, which combined with this being the shortest month, has made the whole thing feel like a whirlwind.

Cologne – Haux on Something to Remember – EP

Angel – H. Kenneth on Angel – Single

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

Call Him – Noah Cunane on Call Him – Single

The Dark – SYML on The Dark – Single

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

Almost Heaven – Isak Danielson on Almost Heaven – Single

Get My Fix – Adam Oh on Get My Fix – Single

Apple Juice – Dan D’Lion & Feder on Apple Juice – Single

Anticipation – Steve Benjamins on Anticipation – Single

New Air – Richard Walters on New Air – Single

Young & Sad – Tom Boy on Young & Sad – Single

Guillotine – Mansionair & NoMBe on Guillotine – Single

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

Falling Fire – Forester on A Range of Light

TRUE – SYML on TRUE – Single

One Last, Last Time – Armen Paul on One Last, Last Time – Single

Crash Into Me – Petey on Crash Into Me – Single

Lost – bülow on Lost – Single

The Light – Richard Walters on The Light – Single

Cold Mine – Fil Bo Riva on Cold Mine – Single

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

2021 – Lauv on 2021 – Single

Killer Queen – Fil Bo Riva on If You’re Right, It’s Alright – EP

Moon River – Au/Ra on Moon River – Single

A Rapture Coming – AJIMAL on As It Grows Dark / Light

How True Is Your Love (Acoustic) – Hannah Grace, Joshua Keogh & Amber Run on How True Is Your Love (Acoustic) – Single

SHE – Winona Oak on SHE – EP

miss u tonight (feat. Edwin Raphael) – it’s matt on miss u tonight (feat. Edwin Raphael) – Single

SIMPLE LIFE – Jake Miller on SIMPLE LIFE – Single

Night Drives – Devan on Night Drives – EP

THE END – Alesso & Charlotte Lawrence on THE END – Single

None Too Deep – Atlas in Motion & Sofia Caterina on None Too Deep – Single

dramatic – Cat & Calmell on dramatic – Single

I Took a Pill in Ibiza – Mike Posner on At Night, Alone.

Winter Rain – Winona Oak on Winter Rain – 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

Drowning – Armen Paul on Drowning – Single

Time to Sink – Edwin Raphael on Time to Sink – Single

Arcade – Duncan Laurence on Arcade – Single1

pink party – Isaac Dunbar on pink party – Single

In the End (feat. Fleurie) [Mellen Gi Remix] – Tommee Profitt on In the End – Single

Stronger (feat. Kesha) – Sam Feldt on Stronger (feat. Kesha) – Single

Throw Me a Line – HAEVN on Throw Me a Line – Single

5AM – Amber Run on 5AM (Deluxe) WEB

Kill V. Maim – Grimes on Art Angels

Blinding Lights – The Weeknd on Blinding Lights – Single

Barrio – Mahmood on Barrio – Single

Face My Fears – Isak Danielson on Face My Fears – Single

Unconfident – Spencer William on Unconfident – Single

You Don’t Feel Like Home – Jack in Water on You Don’t Feel Like Home – Single

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

STAY CLOSE – SYML on STAY CLOSE – Single

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

Inuyasha – Mahmood on Inuyasha – Single

Breakout – Sloane & Josh Jacobson on Breakout – Single

twentyfive – Yoste on twentyfive – Single

Broken – Star Seed & Meggie York on Broken – Single

I Took a Pill in Ibiza (Seeb Remix) – Mike Posner on At Night, Alone.

Technologic – Daft Punk on Human After All4

Derezzed – Daft Punk on Tron: Legacy (Amazon MP3 Exclusive Version)

Alejandro – Lady Gaga on The Fame Monster (Deluxe Edition)

Dandelion – Galantis & JVKE on Dandelion – Single

Mold – Jónsi on Mold – Single

Amends – Odette on Herald

Daisy – Ashnikko on DEMIDEVIL5

Boy on the Moon – GROUNDSTROEM on Boy on the Moon – EP

ilomilo (Live From the Film – Billie Eilish: The World’s A Little Blurry) – Billie Eilish on ilomilo (Live From the Film – Billie Eilish: The World’s A Little Blurry) – Single6

cheers – blackbear & Wiz Khalifa on cheers – Single

Loves You Like I Couldn’t Do – Duncan Laurence on Small Town Boy7

High Beams (feat. slowthai) – Flume & HWLS on Hi This Is Flume (Mixtape)

  1. Genuinely shocked to realize this is first appearing in this list; feels like it’s been around for longer than that. I absolutely love this song, for the record. Also, its wikipedia page is great – there’s a whole section of music theory nerdery.
  2. Angel has been in my lists since July of 2019, and I’m Worried About You feels like a strong contender to take its place.
  3. This is one of those songs that feels a little different every time I listen to it. The other day I listened to it with headphones in and the volume up high for the first time, and there was a whole other bass line that I hadn’t noticed before. I love stuff like that.
  4. 28 years. Dang.
  5. Between Amends and Daisy we’re thoroughly over-quota on upsetting album art.
  6. I haven’t watched the documentary yet, but it’s on my list.
  7. In the past few months, Apple got rid of their “search Apple Music” tool and replaced it with “search All The Apple Stores.” Which is, usually, annoying, because the search isn’t paginated and only returns so many results, so occasionally finding a song is very difficult because the song doesn’t make it into the results ahead of a bunch of TV shows or whatever. This time, I had to keep trying different search terms, because I kept getting bodice-rippers from the iBook Store.
Categories
Review

“The Counterfeit Viscount”

Ginn Hale

I don’t think I read the word ‘viscount’ a single time in this book without thinking of Enola Holmes, but that was a fun movie, and this was a fun book, so it all worked out okay.

Like the last book of Hale’s that I read, there’s a great deal of fun worldbuilding going on in a short read. Another alternate history thing, in an entirely different direction, and once again it provides a fun backdrop for a simple enough story.

Admittedly, the mystery itself is a bit convoluted, but it feels like a backdrop for the romance angle, so it can get away with it.

It’s a short read, so I think this short review will suffice. It’s a fun little story, with a silly little romantic plot, and sometimes that’s what you need. If that’s what you’re in the mood for, give it a read.