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
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
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 }
Code language: Swift (swift)

(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 }
Code language: Swift (swift)

After that, write your request:

let requestString = "SELECT ParentID, SUM(Value) FROM child GROUP BY ParentID"
Code language: Swift (swift)

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""#
Code language: Swift (swift)

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

Finally, make the query:

return sqlDatabase.raw(SQLQueryString(requestString)).all(decoding: MyQueryResult.self)
Code language: Swift (swift)
  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
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))) } }
Code language: Swift (swift)

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") } }
Code language: Swift (swift)
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 */)
Code language: Swift (swift)

Then, in your actual schema builder call:

.field("column_name", /* your type */, .sql(defaultValueConstraint), .required)
Code language: Swift (swift)

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() } }
Code language: Swift (swift)

(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.