Categories
Technology

Async/await in Vapor

Swift 5.5 with the new async/await features is going to be released quite soon, and I’m very excited. And while all the new async APIs in UIKit and SwiftUI are neat, they don’t actually catch my eye all that much. As of now, async/await will only be available on the fall-2021 Apple OS releases, and following the general pattern of “current iOS minus one” that means async/await will be available starting… fall of 2022.

However, that’s not the only place I use Swift! I also dabble in server-side Swift when I can, and over there, it’s ready-ish to use now.1

And hey, would you look at that, Vapor’s already got a PR open for adding async variants of the core APIs. Neat! Let’s see how this is implemented.

And, hey, it’s… pretty simple, really! All it takes is getting into the (pre-release!) _NIOConcurrency module, and suddenly any EventLoopFuture has a new function: public func get() async throws -> T. The implementation of that is also lovely and simple, thanks to Swift’s withUnsafeThrowingContinuation and the way SwiftNIO already works.

Now, where I’m more excited about this than Vapor is in Fluent — a lot of the server-side work I do is in C#, using the excellent Entity Framework Core. Database requests are straightforward: MyModel model = await db.MyModels.FirstOrDefaultAsync(m => m.id == id);

With the non-async version of Vapor and Fluent, this is… a bit more difficult to work with. MyModel.find(id, on: req.db).flatMap { model in ... }

At first glance it doesn’t look too bad, but notice the flatMap — we can’t just continue writing our code, we have to move into a new scope. Hello, pyramid of doom.

But now?2 Now, we can await things. Fluent looks more like Entity Framework all of a sudden: let model = try await MyModel.find(id, on: req.db)

That’s already a much nicer API to work with, and it delivers on the promise of async/await: much simpler flow of code. I’m excited to untangle some of my more complex flatMap pyramids.

What I’m still curious about, and will need to do some testing with, is to see how well it does with concurrency. It’s a known issue in Entity Framework that you can’t run multiple requests against a single DbContext; what I’m wondering is if Fluent has this same limitation, or if I can safely do something like:

async let model1 = try MyModel.find(id1, on: req.db)
async let model2 = try MyModel.find(id2, on: req.db)
let result = MyResult(model1: await model1, model2: await model2)

That, I don’t yet know! I’ll have to do some experimenting and try it — which may be a future blog post. Still, even if I have to immediately await everything, the improvements to my code’s legibility will be worth it.

  1. Disclaimer: Swift 5.5 is, as of this writing, pre-release; similarly, the versions of Swift-NIO and Vapor that provide async APIs are pre-release. Don’t use these in production.
  2. Well, soon: this as another draft PR as of this writing.
Categories
Programming

Islands.swift

Back in undergrad, I did a lot of programming challenges – the sort of thing that shows up as a somewhat-contrived question in an interview. Great way to learn about automated testing, though, as in a school environment it’s basically an automated test suite where you aren’t allowed to see the tests.1

I’ve been wanting to brush up on algorithms a bit, and figured this would be a good way to do it. This time around, though, I’m using Swift; C++ was a fine language to learn, but I have exactly no desire to use it anymore.

So, the first problem I found whilst aimlessly googling was “Islands”. Given a grid of some sort (could be [[Bool]], although in this case I’ve done one as [[Character]], with '1' and '0' being the inputs2), count the number of islands in it.

An island, in this case, is any contiguous collection of '1' is in the grid, while all the the '0's are ocean.

Step One: Ask Questions

First question: what does ‘contiguous’ mean? Can we move diagonally, or only in the four cardinal directions?

Only the four cardinal directions, in this example.

Alright. Next, what happens at the edges? Do we Asteroids-style loop around, or is it just ‘out of bounds == ocean’?

Out of bounds is ocean.

Excellent! Means we don’t have any literal edge cases.

Final question before I start actually digging into the problem, how are we hooking into the test case?

protocol Islands {
  func numIslands(_ grid: [[Character]]) -> Int
}

Alright, simple enough. Let’s get cracking.

Step Two: Brainstorm

My first thought upon seeing a grid is “flood fill.” Now, this isn’t quite a flood fill, because the whole point is that it’s not all interconnected, but that at least gives me a starting point – for a flood fill, you want recursion. And you want to remember where you’ve been, so you can make a base case – otherwise, you’ll just loop forever. O(infinity) isn’t really ideal.

So, what’s the actual algorithm here?

Simple enough: for each point on the grid, check if it’s part of a new island. If it is, add one to our count of islands; if it isn’t, don’t. Move on to the next point on the grid.

Now, how do we check if it’s a new island? Also pretty simple: check if we’ve been here before; if we have, it’s not a new island. Then check if it’s an island; if it isn’t, it’s also not a new island. And, now that we know it’s a new island, we go recursive – mark that we’ve been to this spot, and flood fill our way across the neighboring tiles on the grid until we run out of island.

Step Three: Code

class Solution: Islands {
    private var visited: [[Bool]] = [] // (1)
    private var grid: [[Character]] = [] // (2)
    
    @discardableResult
    private func isNewIsland(x: Int, y: Int) -> Bool { // (3)
        guard x >= 0 else { return false }
        guard y >= 0 else { return false }
        guard x < grid.count else { return false }
        guard y < grid[0].count else { return false }
        if (visited[x][y]) { return false } 
        visited[x][y] = true
        if (grid[x][y] == "1") {
            // visit all neighbors
            isNewIsland(x: x+1, y: y)
            isNewIsland(x: x-1, y: y)
            isNewIsland(x: x, y: y+1)
            isNewIsland(x: x, y: y-1)
            return true
        } else {
            return false
        }
    }
    
    func numIslands(_ grid: [[Character]]) -> Int { // (4) 
        // precondition checks
        guard grid.count > 0 else {
            return 0
        }
        guard grid[0].count > 0 else {
            return 0
        }
        // reset visited state
        let subItem = Array<Bool>(repeating: false, count: grid[0].count)
        self.visited = Array<Array<Bool>>(repeating: subItem, count: grid.count)
        self.grid = grid
        var islandCount = 0
        for i in 0..<grid.count {
            for j in 0..<grid[i].count {
                if isNewIsland(x: i, y: j) {
                    islandCount = islandCount + 1
                }
            }
        }
        return islandCount
    }
}

Let’s go through this bit by bit.

  1. visited is key – we need to know where we’ve been. Unlike the folks making the problem statement, I actually know what my data type is – [[Bool]]
  2. This… is mostly for ease of passing it around. While I could write up my isNewIsland function in a purely-functional sense, with it taking in the coordinates, visited state, and grid, and then outputting the result… well, that’s kinda silly, really.
  3. isNewIsland is the bulk of the work. Let’s go through it.
    • The first four lines are checking that we’ve got valid input. This saves us from needing to check the range every time we call the method; if we try to do something that we can’t, it’ll just return false. Admittedly, I’m not checking that the grid properly exists before using it, but that’s what private is for – this is an implementation detail.
    • Next, check if we’ve visited this spot before. If we have, we’re done – it’s not a new island!
    • Before we go any further, mark this spot as visited! Important, because we may be recursing shortly, and we don’t want to get caught in an infinite loop.
    • Check if this is an island at all. If it isn’t, we’re done – this wasn’t a new island. If it is, though, we recurse. This is where the @discardableResult comes in – since we’ve got an early bail-out for “we’ve already visited this spot”, we know we’ll never be checking the same island twice, so we actually don’t care about the result of checking the neighboring spaces, we just need it to happen… so that they get marked as visited. And after we’re done marking the whole island as visited, we can finally return true, telling the caller (if they’re listening!) that this was a new island.
  4. Finally, our implementation of numIslands. Mostly it’s just checking for valid inputs – Swift can enforce at compile-time that nobody tries to pass us anything completely the wrong type, but it can’t force people to give us a grid with dimensions greater than 0 in either direction, so we need to check that ourselves. After that, we set up our visited as all-false, copy the grid, and loop through it, counting up our islands.

Step Four: Test

This part, I will mostly leave to your imagination; for myself, I just hit ‘run’ and let the automated tests do their thing. In an interview, Step Two should also include coming up with some test cases and running through them on your pseudo code – and remember to include one or two invalid inputs!

After that, if your tests pass, you could talk about changes you might make. Given my numerous remarks about [[Character]] instead of [[Boolean]], my thought would be to make it generic – have a grid of [[T]] and take a (T) -> Bool closure that tells you whether or not a grid point is an island. I’d also want to comment up the code a bit more, which I’ve neglected to do in this case as I’m writing a blog post around it instead.

Now, having already spoiled the answer, I’ll go ahead and mention that I tried this out at LeetCode; while this one may not be the most fun for you in the immediate wake of my explainer, they’ve got plenty of other solutions you could take a crack at, and a variety of languages to use. Give it a go, it’s kinda fun!

  1. Basically, the inverse of an “opaque box” analysis, if you think about it.
  2. And by “I’ve done one” I mean “the site that gave the example,” because I… wouldn’t use [[Character]] to represent a [[Bool]].
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? {
		...
	}
}

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] = [:]
}

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

extension Endpoint: Hashable { }

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

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

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? {
		...
	}
}

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

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

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

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

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

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

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

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

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

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

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