Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
89
If you’re like me, you often have multiple versions of Xcode installed. One or two beta versions, a stable version, and maybe another version in case the most recent stable version has something weird about it. I also really like mapping my Caps Lock key to something more useful, and after reading Brett Terspstra’s excellent article on making a Hyper key many years ago, I’ve gotten used to hitting Caps Lock + X to jump to Xcode thanks to Karabiner Elements and Alfred. It works a lot like Command + Tab, but doesn’t require hitting Tab until you find it. It basically maps Caps Lock to holding down Command, Option, Control, and Shift all at once, a modifier that is very unlikely to conflict with anything else. Anyway, this setup works by mapping a hotkey to a specific app, which works great 99% of the time, but if you’re working in a beta version of Xcode, and you have that keyboard shortcut mapped to the stable version, it opens the wrong app and can even sometimes get Xcode confused...
a year ago

Improve your reading experience

Logged in users get linked directly to articles resulting in a better reading experience. Please login for free, it takes less than 1 minute.

More from Christian Selig

Curing Mac mini M4 fomo with 3D printing

Spoiler: 3D printed! The colored ports really sell the effect If you’re anything like me, you’ve found the new, tinier Mac mini to be absolutely adorable. But you might also be like me that you either already have an awesome M1 Mac mini that you have no real reason to replace, or the new Mac mini just isn’t something you totally need. While that logic might be sound, but it doesn’t make you want one any less. To help cure this FOMO, I made a cute little 3D printable Mac mini that can sit on your desk and be all cute. But then I had an even better idea, the new Mac mini is powerful sure, but it can’t hold snacks. Or a plant. Or your phone. Or pens/pencils. So I also made some versions you can print that add some cute utility to your desk in the form of the new Mac mini. They’re free of course! Just chuck ’em into your (or your friend’s) 3D printer. It even has all the little details modeled, like the power button, ports (including rear), and fan holes! They’re pretty easy to print, it’s in separate parts for ease of printing the bottom a different color (black) versus the top, then just put a dab of glue (or just use gravity) to keep them together. If you have a multi-color 3D printer, you can color the ports and power LED to make it look extra cool (or just do it after the fact with paint). Here are the different options for your desk! Secret item stash The possibilities for what you can store on your desk are now truly endless. Individually wrapped mints? Key switches? Screws? Paper clips? Rubber bands? Flash drives? Download link: https://makerworld.com/en/models/793456 A very green sorta Mac First carbon neutral Mac is cool and all but what if your Mac mini literally had a plant in it? Every desk needs a cute little plant. Download link: https://makerworld.com/en/models/793464 Phone holder A phone/tablet holder is an essential item on my desk for debugging things, watching a video, or just keeping an eye on an Uber Eats order. Before, guests came over and saw my boring phone stand and judged me, now they come over and think I’m exciting and well-traveled. You can even charge your phone/tablet in portrait mode by pushing the cable through a tunnel made through the Ethernet port that then snakes up to the surface. Download link: https://makerworld.com/en/models/793495 Pen holder The Playdate had the cutest little pen/pencil holder accessory but it unfortunately never shipped and my desk is sad. This will be a nice stand in for your beloved pens, pencils, markers, and Apple Pencils. Download link: https://makerworld.com/en/models/793470 A solid model Or if you just want to stare at it without any frills, you can just print the normal model too! Download link: https://makerworld.com/en/models/793447 Printer recommendation Whenever I post about 3D printing I understandably get a bunch of “Which 3D printer should I buy??” questions. This isn’t sponsored, but I’ve found over the last few years the answer has been pretty easy: something from Bambu Lab. Their printers are somehow super easy to use, well designed, and reasonably priced. Prusas are great too, but I think Bambu is hard to beat for the price. Don’t get an Ender. So if you’re looking for a printer now, Black Friday deals are aplenty so it’s pretty much the best time to pick one up. I’d grab something in their A series if you’re on a budget, or the P1S for a bit more if you can swing it (that’s what I use). https://bambulab.com On the other hand if you just want to print one thing now and again, a lot of local libraries are starting to have 3D printers so that might be worth looking into! And online services exist too (eg: JLCPCB and PCBWay), but if you do it with any regularity a 3D printer is a really fun thing to pick up. Enjoy! ❤️ Learning 3D modeling over the last year has been a ton of fun so I love a good excuse to practice, and shout out to Jerrod Hofferth and his amazing 3D printable Mac mini tower (that you should totally download) for the idea to solve my desire with some 3D printing! Also, the models are almost certainly not accurate down to the micrometer as I don’t actually have one, they’re based off Apple’s measurements as well as measuring screenshots. But it should be close! If you have a multi-color 3D printer, the linked models have the colors built-in for your ready to go, but if you want to print it in single-colors I also made versions available with the top and bottom separate as well as the logo, so you can print them separately in the individual colors then connect them with a touch of super glue or something.

6 months ago 57 votes
Introducing Tiny Storage: a small, lightweight UserDefaults replacement

Hey I'm a developer not an artist Following my last blog post about difficulties surrounding UserDefaults and edge cases that lead to data loss (give it a read if you haven’t, it’s an important precursor to this post!), I wanted to build something small and lightweight that would serve to fix the issues I was encountering with UserDefaults and thus TinyStorage was born! It’s open source so you can use it in your projects too if would like. GitHub link 🐙 Overview As mentioned in that blog post, UserDefaults has more and more issues as of late with returning nil data when the device is locked and iOS “prelaunches” your app, leaving me honestly sort of unable to trust what UserDefaults returns. Combined with an API that doesn’t really do a great job of surfacing whether it’s available, you can quite easily find yourself in a situation with difficult to track down bugs and data loss. This library seeks to address that fundamentally by not encrypting the backing file, allowing more reliable access to your saved data (if less secure, so don’t store sensitive data), with some niceties sprinkled on top. This means it’s great for preferences and collections of data like bird species the user likes, but not for sensitive details. Do not store passwords/keys/tokens/secrets/diary entries/grammy’s spaghetti recipe, anything that could be considered sensitive user information, as it’s not encrypted on the disk. But don’t use UserDefaults for sensitive details either as UserDefaults data is still fully decrypted when the device is locked so long as the user has unlocked the device once after reboot. Instead use Keychain for sensitive data. As with UserDefaults, TinyStorage is intended to be used with relatively small, non-sensitive values. Don’t store massive databases in TinyStorage as it’s not optimized for that, but it’s plenty fast for retrieving stored Codable types. As a point of reference I’d say keep it under 1 MB. This reliable storing of small, non-sensitive data (to me) is what UserDefaults was always intended to do well, so this library attempts to realize that vision. It’s pretty simple and just a few hundred lines, far from a marvel of filesystem engineering, but just a nice little utility hopefully! (Also to be clear, TinyStorage is not a wrapper for UserDefaults, it is a full replacement. It does not interface with the UserDefaults system in any way.) Features Reliable access: even on first reboot or in application prewarming states, TinyStorage will read and write data properly Read and write Swift Codable types easily with the API Similar to UserDefaults uses an in-memory cache on top of the disk store to increase performance Thread-safe through an internal DispatchQueue so you can safely read/write across threads without having to coordinate that yourself Supports storing backing file in shared app container Uses NSFileCoordinator for coordinating reading/writing to disk so can be used safely across multiple processes at the same time (main target and widget target, for instance) When using across multiple processes, will automatically detect changes to file on disk and update accordingly SwiftUI property wrapper for easy use in a SwiftUI hierarchy (Similar to @AppStorage) Uses OSLog for logging A function to migrate your UserDefaults instance to TinyStorage Limitations Unlike UserDefaults, TinyStorage does not support mixed collections, so if you have a bunch of strings, dates, and integers all in the same array in UserDefaults without boxing them in a shared type, TinyStorage won’t work. Same situation with dictionaries, you can use them fine with TinyStorage but the key and value must both be a Codable type, so you can’t use [String: Any] for instance where each string key could hold a different type of value. Installation Simply add a Swift Package Manager dependency for https://github.com/christianselig/TinyStorage.git Usage First, either initialize an instance of TinyStorage or create a singleton and choose where you want the file on disk to live. To keep with UserDefaults convention I normally create a singleton for the app container: extension TinyStorage { static let appGroup: TinyStorage = { let appGroupID = "group.com.christianselig.example" let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)! return .init(insideDirectory: containerURL) }() } (You can store it wherever you see fit though, in URL.documentsDirectory is also an idea for instance!) Then, decide how you want to reference your keys, similar to UserDefaults you can use raw strings, but I recommend a more strongly-typed approach, where you simply conform a type to TinyStorageKey and return a var rawValue: String and then you can use it as a key for your storage without worrying about typos. If you’re using something like an enum, making it a String enum gives you this for free, so no extra work! After that you can simply read/write values in and out of your TinyStorge instance: enum AppStorageKeys: String, TinyStorageKey { case likesIceCream case pet case hasBeatFirstLevel } // Read let pet: Pet? = TinyStorage.appGroup.retrieve(type: Pet.self, forKey: AppStorageKeys.pet) // Write TinyStorage.appGroup.store(true, forKey: AppStorageKeys.likesIceCream) (If you have some really weird type or don’t want to conform to Codable, just convert the type to Data through whichever means you prefer and store that, as Data itself is Codable.) If you want to use it in SwiftUI and have your view automatically respond to changes for an item in your storage, you can use the @TinyStorageItem property wrapper. Simply specify your storage, the key for the item you want to access, and specify a default value. @TinyStorageItem(key: AppStorageKey.pet, storage: .appGroup) var pet: = Pet(name: "Boots", species: .fish, hasLegs: false) var body: some View { Text(pet.name) } You can even use Bindings to automatically read/write. @TinyStorageItem(key: AppStorageKeys.message, storage: .appGroup) var message: String = "" var body: some View { VStack { Text("Stored Value: \(message)") TextField("Message", text: $message) } } It also addresses some of the annoyances of @AppStorage, such as not being able to store collections: @TinyStorageItem(key: "names", storage: .appGroup) var names: [String] = [] Or better support for optional values: @TinyStorageItem(key: "nickname", storage: .appGroup) var nickname: String? = nil // or "Cool Guy" Hope it’s handy! If you like it or have any feedback let me know! I’m going to start slowly integrating it into Pixel Pals and hopefully solve a few bugs in the process.

7 months ago 85 votes
Beware UserDefaults: a tale of hard to find bugs, and lost data

Excuse the alarmist title, but I think it’s justified, as it’s an issue that’s caused me a ton of pain in both support emails and actually tracking it down, so I want to make others aware of it so they don’t similarly burned. Brief intro For the uninitiated, UserDefaults (née NSUserDefaults) is the de facto iOS standard for persisting non-sensitive, non-massive data to “disk” (AKA offline). In other words, are you storing some user preferences, maybe your user’s favorite ice cream flavors? UserDefaults is great, and used extensively from virtually every iOS app to Apple sample code. Large amount of data, or sensitive data? Look elsewhere! This is as opposed to just storing it in memory where if the user restarts the app all the data is wiped out. It’s a really handy tool with a ton of nice, built-in things for you: No needing to mess with writing to files yourself, and better yet, no need to coordinate when to persist values back to the disk Easy to share data between your app’s main target and secondary targets (like a widget target) Automatic serialization and deserialization: just feed in a String, Date, Int, and UserDefaults handles turning it into bytes and back from bytes Thread-safe! So it’s no wonder it’s used extensively. But yeah, keep the two limitations in mind that Apple hammers home: Don’t store sensitive data in UserDefaults, that’s what Keychain is for Don’t store large amounts of data in UserDefaults, use something like Core Data or Swift Data Okay, so what’s the problem Turns out, sometimes you can request your saved data back from UserDefaults and it… just won’t have it! That’s a pretty big issue for a system that’s supposed to reliably store data for you. This can amount to an even bigger issue that leads to permanent data loss. Imagine a situation where a user has been meticulously opening your app for 364 days in a row. On day 365, your app promised a cool reward! When the user last closed the app, you stored 364 to UserDefaults. The user wakes up on day 365, excited for their reward: App launches App queries UserDefaults for how many days in a row the user has opened the app App returns 0 (UserDefaults is mysteriously unavailable so its API returns the default integer value of 0) It’s a new day, so you increment that value by 1, so that 0 changes to 1 Save that new value back to UserDefaults Now, instead of your user having a fun celebration, their data has been permanently overwritten and reset! They are having a Sad Day™. It basically means, if at any point you trust UserDefaults to accurately return your data (which you know, sounds like a fair assumption) you might just get incorrect data, which you then might make worse by overwriting good data with. And remember, you’re not meant to store sensitive data in UserDefaults, but even if it’s not sensitive data it might be valuable. The user’s day streak above is not sensitive data that would be bad if leaked online like a password, but it is valuable to that user. In fact I’d argue any data persisted to the disk is valuable, otherwise you wouldn’t be saving it. And you should be always be able to trust an API to reliably save your data. What??? How is this happening? 😵‍💫 As I understand it, there’s basically two systems coming together (and working incorrectly, if you ask me) to cause this: 1. Sensitive data encryption When using Keychain or files directly, as a developer you can mark data that should be encrypted until the device is unlocked by Face ID/Touch ID/passcode. This way if you’re storing a sensitive data like a token or password on the device, the contents are encrypted and thus unreadable until the device is unlocked. This meant if the device was still locked, and you, say, had a Lock Screen Widget that performed an API request, you would have to show placeholder data until the user unlocked the device, because the sensitive data, namely the user’s API token, was encrypted and unable to be used by the app to fetch and show data until the user unlocked the device. Not the end of the world, but something to keep in mind for secure data like API tokens, passwords, secrets, etc. 2. Application prewarming Starting with iOS 15, iOS will sometimes wake up your application early so that when a user launches it down the road it launches even quicker for them, as iOS was able to do some of the heavy lifting early. This is called prewarming. Thankfully per Apple, your application doesn’t fully launch, it’s just some processes required to get your app working: Prewarming executes an app’s launch sequence up until, but not including, when main() calls UIApplicationMain(::::). Okay, so what happened with these two? It seems at some point, even though UserDefaults is intended for non-sensitive information, it started getting marked as data that needs to be encrypted and cannot be accessed until the user unlocked their device. I don’t know if it’s because Apple found developers were storing sensitive data in there even when they shouldn’t be, but the result is even if you just store something innocuous like what color scheme the user has set for your app, that theme cannot be accessed until the device is unlocked. Again, who cares? Users have to unlock the device before launching my app, right? I thought so too! It turns out, even though Apple’s prewarming documentation states otherwise, developers have been reporting for years that that’s just wrong, and your app can effectively be fully launched at any time, including before the device is even unlocked. Combining this with the previous UserDefaults change, you’re left with the above situation where the app is launched with crucial data just completely unavailable because the device is still locked. UserDefaults also doesn’t make this clear at all, which it could do by for instance returning nil when trying to access UserDefaults.standard if it’s unavailable. Instead, it just looks like everything is as it should be, except none of your saved keys are available anymore, which can make your app think it’s in a “first launch after install” situation. The whole point of UserDefaults is that it’s supposed to reliably store simple, non-sensitive data so it can be accessed whenever. The fact that this has now changed drastically, and at the same time your app can be launched effectively whenever, makes for an incredibly confusing, dangerous, and hard to debug situation. And it’s getting worse with Live Activities If you use Live Activities at all, the cool new API that puts activities in your Dynamic Island and Lock Screen, it seems if your app has an active Live Activity and the user reboots their device, virtually 100% of the time the above situation will occur where your app is launched in the background without UserDefaults being available to it. That means the next time your user actually launches the app, if at any point during your app launching you trusted the contents of UserDefaults, your app is likely in an incorrect state with incorrect data. This bit me badly, and I’ve had users email me over time that they’ve experienced data loss, and it’s been incredibly tricky to pinpoint why. It turns out it’s simply because the app started up, assuming UserDefaults would return good data, and when it transparently didn’t, it would ultimately overwrite their good data with the returned bad data. I’ve talked to a few other developers about this, and they’ve also reported random instances of users being logged out or losing data, and after further experimenting been able to now pinpoint that this is what caused their bug. It happened in past apps to me as well (namely users getting signed out of Apollo due to a key being missing), and I could never figure out why, but this was assuredly it. If you’ve ever scratched your head at a support email over a user’s app being randomly reset, hopefully this helps! I don’t like this ☹️ I can’t overstate what a misstep I think this was. Security is always a balance with convenience. Face ID and Touch ID strike this perfectly; they’re both ostensibly less secure per Apple’s own admission than, say, a 20 digit long password, but users are much more likely to adopt biometric security so it’s a massive overall win. Changing UserDefaults in this way feels more on the side of “Your company’s sysadmin requiring you to change your password every week”: dubious security gains at the cost of user productivity and headaches. But enough moaning, let’s fix it. Solution 1 Because iOS is now seemingly encrypting UserDefaults, the easiest solution is to check UIApplication.isProtectedDataAvailable and if it returns false, subscribe to NotificationCenter for when protectedDataDidBecomeAvailableNotification is fired. This was previously really useful for knowing when Keychain or locked files were accessible once the device was unlocked, but it now seemingly applies to UserDefaults (despite not being mentioned anywhere in its documentation or UserDefault’s documentation 🙃). I don’t love this solution, because it effectively makes UserDefaults either an asynchronous API (“Is it available? No? Okay I’ll wait here until it is.”), or one where you can only trust its values sometimes, because unlike the Keychain API for instance, UserDefaults API itself does not expose any information about this when you try to access it when it’s in a locked state. Further, some developers have reported UserDefaults still being unavailable even once isProtectedDataAvailable returns true. Solution 2 For the mentioned reasons, I don’t really like/trust Solution 1. I want a version of UserDefaults that acts like what it says on the tin: simply, quickly, and reliably retrieve persisted, non-sensitive values. This is easy enough to whip up ourselves, we just want to keep in mind some of the things UserDefaults handles nicely for us, namely thread-safety, shared between targets, and an easy API where it serializes data without us having to worry about writing to disk. Let’s quickly show how we might approach some of this. UserDefaults is fundamentally just a plist file stored on disk that is read into memory, so let’s create our own file, and instead of marking it as requiring encryption like iOS weirdly does, we’ll say that’s not required: // Example thing to save let favoriteIceCream = "chocolate" // Save to your app's shared container directory so it can be accessed by other targets outside main let appGroupID = "" // Get the URL for the shared container guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID) else { fatalError("App Groups not set up correctly") } // Create the file URL within the shared container let fileURL = containerURL.appendingPathComponent("Defaults") do { let data = favoriteIceCream.data(using: .utf8) try data.write(to: fileURL) // No encryption please I'm just storing the name of my digital cow Mister Moo try FileManager.default.setAttributes([.protectionKey: .none], ofItemAtPath: fileURL.path) print("File saved successfully at \(fileURL)") } catch { print("Error saving file: \(error.localizedDescription)") } (Note that you could theoretically modify the system UserDefaults file in the same way, but Apple documentation recommends against touching the UserDefaults file directly.) Next let’s make it thread safe by using a DispatchQueue. private static let dispatchQueue = DispatchQueue(label: "DefaultsQueue") func retrieveFavoriteIceCream() -> String? { return dispatchQueue.sync { guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "app-group-id") else { return nil } let fileURL = containerURL.appendingPathComponent(fileName) do { let data = try Data(contentsOf: fileURL) return String(data: data, encoding: .utf8) } catch { print("Error retrieving file: \(error.localizedDescription)") return nil } } } func save(favoriteIceCream: String) { return dispatchQueue.sync { guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "app-group-id") else { return } let fileURL = containerURL.appendingPathComponent(fileName) do { let data = favoriteIceCream.data(using: .utf8) try data.write(to: fileURL) try FileManager.default.setAttributes([.protectionKey: .none], ofItemAtPath: fileURL.path) print("File saved successfully at \(fileURL)") } catch { print("Error saving file: \(error.localizedDescription)") } } } (You probably don’t need a concurrent queue for this, so I didn’t.) But with that we have to worry about data types, let’s just make it so long as the type conforms to Codable we can save or retrieve it: func saveCodable(_ codable: Codable, forKey key: String) { do { let data = try JSONEncoder().encode(codable) // Persist raw data bytes to a file like above } catch { print("Unable to encode \(codable): \(error)") } } func codable<T: Codable>(forKey key: String, as type: T.Type) -> T? { let data = // Fetch raw data from disk as done above do { return try JSONDecoder().decode(T.self, from: data) } catch { print("Error decoding \(T.self) for key \(key) with error: \(error)") return nil } } // Example usage: let newFavoriteIceCream = "strawberry" saveCodable(newFavoriteIceCream, forKey: "favorite-ice-cream") let savedFavoriteIceCream = codable(forKey: "favorite-ice-cream", as: String.self) Put those together, wrap it in a nice little library, and bam, you’ve got a UserDefaults replacement that acts as you would expect. In fact if you like the encryption option you can add it back pretty easily (don’t change the file protection attributes) and you could make it clear in the API when the data is inaccessible due to the device being locked, either by throwing an error, making your singleton nil, awaiting until the device is locked, etc. End Maybe this is super obvious to you, but I’ve talked to enough developers where it wasn’t, that I hope in writing this it can save you the many, many hours I spent trying to figure out why once in a blue moon a user would be logged out, or their app state would look like it reset, or worst of all: they lost data.

7 months ago 68 votes
Juno for YouTube has been removed from the App Store

For those not aware, a few months ago after reaching out to me, YouTube contacted the App Store stating that Juno does not adhere to YouTube guidelines and modifies the website in a way they don’t approve of, and alludes to their trademarks and iconography. I don’t personally agree with this, as Juno is just a web view, and acts as little more than a browser extension that modifies CSS to make the website and video player look more “visionOS” like. No logos are placed other than those already on the website, and the “for YouTube” suffix is permitted in their branding guidelines. I stated as much to YouTube, they wouldn’t really clarify or budge any, and as a result of both parties not being able to come to a conclusion I received an email a few minutes ago from Apple that Juno has been removed from the App Store. Juno was a fun hobby project for me to build. As a developer I wanted to get some experience building for the Vision Pro, and as a user I wanted a nice way to watch YouTube on this cool new device. As a result, I really enjoyed building Juno, but it was always something I saw as fundamentally a little app I built for fun. Because of that, I have zero desire to spin this into a massive fight akin to what happened with Reddit years ago. That’s kind of the opposite of fun. I hope that’s understandable. For those who have Juno, to my knowledge it should continue to work fine until/unless YouTube updates in some fashion that breaks stuff. Sorry it had to end this way, I had some really cool stuff planned for it that I think would have been a lot of fun! It’s been genuinely awesome hearing all the kind words from Vision Pro users who have loved the app. 🫡

7 months ago 66 votes
Server side Live Activities guide

iOS 17.2 gained the capability to start Live Activities from a server, which is pretty cool and handy! I’ve been playing around with it a bit and found some parts a bit confusing, so I thought I’d do a little write up for future me as well as anyone else who could benefit! (For the uninitiated, Live Activities are the cool iOS feature that adds a live view of a specific activity to your Dynamic Island (if available) and Lock Screen, for example to see how your Uber Eats order is coming along.) Overview of starting a Live Activity server-side It’s pretty straightforward, just a few steps: Get token In your AppDelegate’s didFinishLaunching or somewhere very early in the lifecycle, start an async sequence to listen for a pushToStartToken so you can get the token: Task { for await pushToken in Activity<IceCreamWidgetAttributes>.pushToStartTokenUpdates { let pushTokenString = pushToken.reduce("") { $0 + String(format: "%02x", $1) } print("Our push token is: \(pushTokenString)") } } Use token to start Live Activity server-side Now that you have the token, we use that to send a push notification (using APNs) to start the Live Activity on the user’s device. There’s lots of server side libraries for this, or you can just use curl, or you can an online site to test like this one (in the case of the latter where you’re uploading your token to random sites, please create a sample token that you delete afterward). The key points that differ from sending a “normal” push notification: Headers Set your push token to the pushToStartToken you received above Set the topic to your app’s normal bundle ID with an added suffix of .push-type.liveactivity Set the priority to 5 for low priority or 10 for a high priority notification (does not seem like any values in between work) Set apns-push-type to liveactivity Payload timestamp field with the current unix timestamp event field set to start attributes-type set to the name of your Swift Live Activities attributes struct attributes with a dictionary representing your Swift Live Activity attributes content-state with the initial content state as a dictionary, similar to attributes alert field set with a title and a body (will silently fail if you just set alert to a string like the old days) Note that you cannot use the old certificate based authentication and instead have to use token based authentication and http2. Send the http request and it should start the Live Activity on the iOS device! 🎉 Aside Sending push notifications is kinda complicated, so you likely want a server-side library. I wanted to play around with Node for this for the first time, and in case you go down that path, in September 2024 Node is in a weirdly lacking spot for APNs libraries. The de facto one is abandoned, the community replacement for it doesn’t work with TypeScript, and there’s a third option with TypeScript support but it isn’t super popular and has some issues. I ended up going back to Go, and there’s an excellent APNs library there. It’s broken on iOS 17 On iOS 17, getting the above push token is really hard (you can seemingly only get it once). I tried for ages to get the token before stumbling upon a thread on the Apple forums where a user said to delete the app, reboot your device, then fresh install it. Sure enough that worked and the token displayed, but if I just rebooted the device, or just reinstalled the app, it wouldn’t. Had to do all of them. And no it didn’t change if I used a release configuration. I tried this on iOS 17.6.1 (latest iOS 17 release at the time of writing). It does not seem to be an issue at all on iOS 18. The difficulty in acquiring it makes it incredibly hard to use on iOS 17 if you add in the feature in an update and the user isn’t getting your app from a fresh install, to the extent that I can’t really trust its reliability on iOS 17 as a feature you could advertise, for instance. John Gruber recently wrote about the Apple Sports app and wondered why its Live Activities feature is iOS 18 only. A reader wrote in to mention the new broadcast push notifications feature requiring iOS 18, and that well may be it, but I’d say it’s equally as likely that it just doesn’t work reliably enough on iOS 17 for even Apple to bother. Update/end the activity This part admittedly confused me a bit. The docs state: Send the push-to-start token to your server and use the pushToStartTokenUpdates sequence to receive token updates. Similar to the update token, update it on your server when needed and invalidate the old token. While the system starts the new Live Activity and wakes up your app, you receive the push token you use for updates. To update and end the Live Activity on devices that aren’t running iOS 18 or iPadOS 18, use this update push token as if you obtained it by starting a Live Activity from within your app. I assumed that this all operated through that same pushToStartTokenUpdates, because as soon as you start the activity server-side, your app wakes up, and your pushToStartTokenUpdates async sequence fires again with a “new” token. However the “new” token is just the same one that you started the activity with, and if you try to end your activity server-side with this token, nothing happens. Turns out, your pushToStartTokenUpdates is (per the name!) only able to start Live Activities. Not sure why it fires a second time with the same token, but you do want to use that async sequence to monitor for changes to the start token, because it might change and the next time you want to start a new Live Activity you’ll need that token. To update/end your Live Activity, what you actually want to do is create a separate async sequence to monitor your app for Live Activities that get created, and then monitor its push token: // Listen for local Live Activity updates for await activity in Activity<IceCreamWidgetAttributes>.activityUpdates { // Upon finding one, listen for its push token (it is not available immediately!) for await pushToken in activity.pushTokenUpdates { let pushTokenString = pushToken.reduce("") { $0 + String(format: "%02x", $1) } print("New activity detected with push token: \(pushTokenString)") } } iOS will also call this async sequence on start of a new Live Activity from a server, and you use that token to update/end it. I’m not blaming the documentation on this, I understand it as it is written now, but I wanted to clarify in case anyone else gets confused. Once your server is made aware of this token, you can end your Live Activity server-side with the following changes from the above start considerations: Payload: event should be end or update Payload: If ending you might want a dismissal-date unix timestamp. If you don’t set this, iOS will immediately remove the Live Activity from the Dynamic Island but leave it on the Lock Screen for up to four hours. You may want this, but you can control how long it stays there by setting dismissal-date, if you set it to now or in the past it will remove it upon receipt of the notification. Payload: Send a new content-state if updating (optional if ending, if ending and you want to leave it on the lock screen (see previous point) you can set a content-state which will serve as the final content state for the Live Activity) Payload: Do not send attributes or attributes-type as these are intended to be immutable through the life of the Live Activity There’s other interesting ones that you might want to consider but aren’t as important, like stale-date, discussed in the docs. That’s it! That should cover most things! I want to thank the incredible Francesc Bruguera for helping me get unstuck a few places.

7 months ago 85 votes

More in technology

Home is where the home server is

I moved recently, and so did my home server. You might have noticed it due to the downtime. This time I have built a dedicated shelf for it, which allows for more flexibility and room for additional expensive ideas. The internet connection is a fiber line, which is fantastic for a place that’s generally considered to be in the countryside. I had to hire a guy at the last place in Tallinn (capital of Estonia) to pull a fiber line from the basement to the apartment, with my own money, so I’m very happy that I don’t have to do it here. And yes, the ThinkPad T430 is still a solid home server. I had an issue with my battery calibration script resulting in the machine being turned off, but I fixed it by disabling it, at the cost of the battery probably dying soon. Seems like a tlp and/or Linux kernel issue that has surfaced recently, as it also happened on a different ThinkPad laptop when I last tried it. I can’t really remove the battery, because the “power on with AC attach” setting only works when the battery is connected and charged. The server/wardrobe/closet room is slightly chillier compared to the rest of the environment, meaning that the temperatures are also slightly lower. I also have an option to do some crazy ventilation experiments in the winter, but that will have to wait for a bit, mainly because it’s spring. I’m genuinely surprised that the Wi-Fi 5 signal is coming through the closet quite adequately, with the whole apartment being covered with at least 50 Mbit/s speeds, and over 300 Mbit/s when near the closet, which is about the maximum speed that I can achieve from the access point in ideal conditions.

4 hours ago 1 votes
Optimize maintenance with the Arduino Rileva ME Opta Bundle

When your machines run smoothly, your business can go far. That’s why condition monitoring – once a “nice to have” – is quickly becoming a must in maintenance strategies across industrial settings. But most dedicated systems can be complex to set up or difficult to scale. To make things easier, we’re introducing the Arduino Rileva ME Opta […] The post Optimize maintenance with the Arduino Rileva ME Opta Bundle appeared first on Arduino Blog.

2 days ago 4 votes
Dr. Dobb's Journal Interviews Jef Raskin (1986)

They discuss interface design and Raskin's hatred of the mouse.

3 days ago 5 votes
2025-05-11 air traffic control

Air traffic control has been in the news lately, on account of my country's declining ability to do it. Well, that's a long-term trend, resulting from decades of under-investment, severe capture by our increasingly incompetent defense-industrial complex, no small degree of management incompetence in the FAA, and long-lasting effects of Reagan crushing the PATCO strike. But that's just my opinion, you know, maybe airplanes got too woke. In any case, it's an interesting time to consider how weird parts of air traffic control are. The technical, administrative, and social aspects of ATC all seem two notches more complicated than you would expect. ATC is heavily influenced by its peculiar and often accidental development, a product of necessity that perpetually trails behind the need, and a beneficiary of hand-me-down military practices and technology. Aviation Radio In the early days of aviation, there was little need for ATC---there just weren't many planes, and technology didn't allow ground-based controllers to do much of value. There was some use of flags and signal lights to clear aircraft to land, but for the most part ATC had to wait for the development of aviation radio. The impetus for that work came mostly from the First World War. Here we have to note that the history of aviation is very closely intertwined with the history of warfare. Aviation technology has always rapidly advanced during major conflicts, and as we will see, ATC is no exception. By 1913, the US Army Signal Corps was experimenting with the use of radio to communicate with aircraft. This was pretty early in radio technology, and the aircraft radios were huge and awkward to operate, but it was also early in aviation and "huge and awkward to operate" could be similarly applied to the aircraft of the day. Even so, radio had obvious potential in aviation. The first military application for aircraft was reconnaissance. Pilots could fly past the front to find artillery positions and otherwise provide useful information, and then return with maps. Well, even better than returning with a map was providing the information in real-time, and by the end of the war medium-frequency AM radios were well developed for aircraft. Radios in aircraft lead naturally to another wartime innovation: ground control. Military personnel on the ground used radio to coordinate the schedules and routes of reconnaissance planes, and later to inform on the positions of fighters and other enemy assets. Without any real way to know where the planes were, this was all pretty primitive, but it set the basic pattern that people on the ground could keep track of aircraft and provide useful information. Post-war, civil aviation rapidly advanced. The early 1920s saw numerous commercial airlines adopting radio, mostly for business purposes like schedule coordination. Once you were in contact with someone on the ground, though, it was only logical to ask about weather and conditions. Many of our modern practices like weather briefings, flight plans, and route clearances originated as more or less formal practices within individual airlines. Air Mail The government was not left out of the action. The Post Office operated what may have been the largest commercial aviation operation in the world during the early 1920s, in the form of Air Mail. The Post Office itself did not have any aircraft; all of the flying was contracted out---initially to the Army Air Service, and later to a long list of regional airlines. Air Mail was considered a high priority by the Post Office and proved very popular with the public. When the transcontinental route began proper operation in 1920, it became possible to get a letter from New York City to San Francisco in just 33 hours by transferring it between airplanes in a nearly non-stop relay race. The Post Office's largesse in contracting the service to private operators provided not only the funding but the very motivation for much of our modern aviation industry. Air travel was not very popular at the time, being loud and uncomfortable, but the mail didn't complain. The many contract mail carriers of the 1920s grew and consolidated into what are now some of the United States' largest companies. For around a decade, the Post Office almost singlehandedly bankrolled civil aviation, and passengers were a side hustle [1]. Air mail ambition was not only of economic benefit. Air mail routes were often longer and more challenging than commercial passenger routes. Transcontinental service required regular flights through sparsely populated parts of the interior, challenging the navigation technology of the time and making rescue of downed pilots a major concern. Notably, air mail operators did far more nighttime flying than any other commercial aviation in the 1920s. The post office became the government's de facto technical leader in civil aviation. Besides the network of beacons and markers built to guide air mail between cities, the post office built 17 Air Mail Radio Stations along the transcontinental route. The Air Mail Radio Stations were the company radio system for the entire air mail enterprise, and the closest thing to a nationwide, public air traffic control service to then exist. They did not, however, provide what we would now call control. Their role was mainly to provide pilots with information (including, critically, weather reports) and to keep loose tabs on air mail flights so that a disappearance would be noticed in time to send search and rescue. In 1926, the Watres Act created the Aeronautic Branch of the Department of Commerce. The Aeronautic Branch assumed a number of responsibilities, but one of them was the maintenance of the Air Mail routes. Similarly, the Air Mail Radio Stations became Aeronautics Branch facilities, and took on the new name of Flight Service Stations. No longer just for the contract mail carriers, the Flight Service Stations made up a nationwide network of government-provided services to aviators. They were the first edifices in what we now call the National Airspace System (NAS): a complex combination of physical facilities, technologies, and operating practices that enable safe aviation. In 1935, the first en-route air traffic control center opened, a facility in Newark owned by a group of airlines. The Aeronautic Branch, since renamed the Bureau of Air Commerce, supported the airlines in developing this new concept of en-route control that used radio communications and paperwork to track which aircraft were in which airways. The rising number of commercial aircraft made in-air collisions a bigger problem, so the Newark control center was quickly followed by more facilities built on the same pattern. In 1936, the Bureau of Air Commerce took ownership of these centers, and ATC became a government function alongside the advisory and safety services provided by the flight service stations. En route center controllers worked off of position reports from pilots via radio, but needed a way to visualize and track aircraft's positions and their intended flight paths. Several techniques helped: first, airlines shared their flight planning paperwork with the control centers, establishing "flight plans" that corresponded to each aircraft in the sky. Controllers adopted a work aid called a "flight strip," a small piece of paper with the key information about an aircraft's identity and flight plan that could easily be handed between stations. By arranging the flight strips on display boards full of slots, controllers could visualize the ordering of aircraft in terms of altitude and airway. Second, each center was equipped with a large plotting table map where controllers pushed markers around to correspond to the position reports from aircraft. A small flag on each marker gave the flight number, so it could easily be correlated to a flight strip on one of the boards mounted around the plotting table. This basic concept of air traffic control, of a flight strip and a position marker, is still in use today. Radar The Second World War changed aviation more than any other event of history. Among the many advancements were two British inventions of particular significance: first, the jet engine, which would make modern passenger airliners practical. Second, the radar, and more specifically the magnetron. This was a development of such significance that the British government treated it as a secret akin to nuclear weapons; indeed, the UK effectively traded radar technology to the US in exchange for participation in US nuclear weapons research. Radar created radical new possibilities for air defense, and complimented previous air defense development in Britain. During WWI, the organization tasked with defending London from aerial attack had developed a method called "ground-controlled interception" or GCI. Under GCI, ground-based observers identify possible targets and then direct attack aircraft towards them via radio. The advent of radar made GCI tremendously more powerful, allowing a relatively small number of radar-assisted air defense centers to monitor for inbound attack and then direct defenders with real-time vectors. In the first implementation, radar stations reported contacts via telephone to "filter centers" that correlated tracks from separate radars to create a unified view of the airspace---drawn in grease pencil on a preprinted map. Filter center staff took radar and visual reports and updated the map by moving the marks. This consolidated information was then provided to air defense bases, once again by telephone. Later technical developments in the UK made the process more automated. The invention of the "plan position indicator" or PPI, the type of radar scope we are all familiar with today, made the radar far easier to operate and interpret. Radar sets that automatically swept over 360 degrees allowed each radar station to see all activity in its area, rather than just aircraft passing through a defensive line. These new capabilities eliminated the need for much of the manual work: radar stations could see attacking aircraft and defending aircraft on one PPI, and communicated directly with defenders by radio. It became routine for a radar operator to give a pilot navigation vectors by radio, based on real-time observation of the pilot's position and heading. A controller took strategic command of the airspace, effectively steering the aircraft from a top-down view. The ease and efficiency of this workflow was a significant factor in the end of the Battle of Britain, and its remarkable efficacy was noticed in the US as well. At the same time, changes were afoot in the US. WWII was tremendously disruptive to civil aviation; while aviation technology rapidly advanced due to wartime needs those same pressing demands lead to a slowdown in nonmilitary activity. A heavy volume of military logistics flights and flight training, as well as growing concerns about defending the US from an invasion, meant that ATC was still a priority. A reorganization of the Bureau of Air Commerce replaced it with the Civil Aeronautics Authority, or CAA. The CAA's role greatly expanded as it assumed responsibility for airport control towers and commissioned new en route centers. As WWII came to a close, CAA en route control centers began to adopt GCI techniques. By 1955, the name Air Route Traffic Control Center (ARTCC) had been adopted for en route centers and the first air surveillance radars were installed. In a radar-equipped ARTCC, the map where controllers pushed markers around was replaced with a large tabletop PPI built to a Navy design. The controllers still pushed markers around to track the identities of aircraft, but they moved them based on their corresponding radar "blips" instead of radio position reports. Air Defense After WWII, post-war prosperity and wartime technology like the jet engine lead to huge growth in commercial aviation. During the 1950s, radar was adopted by more and more ATC facilities (both "terminal" at airports and "en route" at ARTCCs), but there were few major changes in ATC procedure. With more and more planes in the air, tracking flight plans and their corresponding positions became labor intensive and error-prone. A particular problem was the increasing range and speed of aircraft, and corresponding longer passenger flights, that meant that many aircraft passed from the territory of one ARTCC into another. This required that controllers "hand off" the aircraft, informing the "next" ARTCC of the flight plan and position at which the aircraft would enter their airspace. In 1956, 128 people died in a mid-air collision of two commercial airliners over the Grand Canyon. In 1958, 49 people died when a military fighter struck a commercial airliner over Nevada. These were not the only such incidents in the mid-1950s, and public trust in aviation started to decline. Something had to be done. First, in 1958 the CAA gave way to the Federal Aviation Administration. This was more than just a name change: the FAA's authority was greatly increased compared tot he CAA, most notably by granting it authority over military aviation. This is a difficult topic to explain succinctly, so I will only give broad strokes. Prior to 1958, military aviation was completely distinct from civil aviation, with no coordination and often no communication at all between the two. This was, of course, a factor in the 1958 collision. Further, the 1956 collision, while it did not involve the military, did result in part from communications issues between separate distinct CAA facilities and the airline's own control facilities. After 1958, ATC was completely unified into one organization, the FAA, which assumed the work of the military controllers of the time and some of the role of the airlines. The military continues to have its own air controllers to this day, and military aircraft continue to include privileges such as (practical but not legal) exemption from transponder requirements, but military flights over the US are still beholden to the same ATC as civil flights. Some exceptions apply, void where prohibited, etc. The FAA's suddenly increased scope only made the practical challenges of ATC more difficult, and commercial aviation numbers continued to rise. As soon as the FAA was formed, it was understood that there needed to be major investments in improving the National Airspace System. While the first couple of years were dominated by the transition, the FAA's second director (Najeeb Halaby) prepared two lengthy reports examining the situation and recommending improvements. One of these, the Beacon report (also called Project Beacon), specifically addressed ATC. The Beacon report's recommendations included massive expansion of radar-based control (called "positive control" because of the controller's access to real-time feedback on aircraft movements) and new control procedures for airways and airports. Even better, for our purposes, it recommended the adoption of general-purpose computers and software to automate ATC functions. Meanwhile, the Cold War was heating up. US air defense, a minor concern in the few short years after WWII, became a higher priority than ever before. The Soviet Union had long-range aircraft capable of reaching the United States, and nuclear weapons meant that only a few such aircraft had to make it to cause massive destruction. Considering the vast size of the United States (and, considering the new unified air defense command between the United States and Canada, all of North America) made this a formidable challenge. During the 1950s, the newly minted Air Force worked closely with MIT's Lincoln Laboratory (an important center of radar research) and IBM to design a computerized, integrated, networked system for GCI. When the Air Force committed to purchasing the system, it was christened the Semi-Automated Ground Environment, or SAGE. SAGE is a critical juncture in the history of the computer and computer communications, the first system to demonstrate many parts of modern computer technology and, moreover, perhaps the first large-scale computer system of any kind. SAGE is an expansive topic that I will not take on here; I'm sure it will be the focus of a future article but it's a pretty well-known and well-covered topic. I have not so far felt like I had much new to contribute, despite it being the first item on my "list of topics" for the last five years. But one of the things I want to tell you about SAGE, that is perhaps not so well known, is that SAGE was not used for ATC. SAGE was a purely military system. It was commissioned by the Air Force, and its numerous operating facilities (called "direction centers") were located on Air Force bases along with the interceptor forces they would direct. However, there was obvious overlap between the functionality of SAGE and the needs of ATC. SAGE direction centers continuously received tracks from remote data sites using modems over leased telephone lines, and automatically correlated multiple radar tracks to a single aircraft. Once an operator entered information about an aircraft, SAGE stored that information for retrieval by other radar operators. When an aircraft with associated data passed from the territory of one direction center to another, the aircraft's position and related information were automatically transmitted to the next direction center by modem. One of the key demands of air defense is the identification of aircraft---any unknown track might be routine commercial activity, or it could be an inbound attack. The air defense command received flight plan data on commercial flights (and more broadly all flights entering North America) from the FAA and entered them into SAGE, allowing radar operators to retrieve "flight strip" data on any aircraft on their scope. Recognizing this interconnection with ATC, as soon as SAGE direction centers were being installed the Air Force started work on an upgrade called SAGE Air Traffic Integration, or SATIN. SATIN would extend SAGE to serve the ATC use-case as well, providing SAGE consoles directly in ARTCCs and enhancing SAGE to perform non-military safety functions like conflict warning and forward projection of flight plans for scheduling. Flight strips would be replaced by teletype output, and in general made less necessary by the computer's ability to filter the radar scope. Experimental trial installations were made, and the FAA participated readily in the research efforts. Enhancement of SAGE to meet ATC requirements seemed likely to meet the Beacon report's recommendations and radically improve ARTCC operations, sooner and cheaper than development of an FAA-specific system. As it happened, well, it didn't happen. SATIN became interconnected with another planned SAGE upgrade to the Super Combat Centers (SCC), deep underground combat command centers with greatly enhanced SAGE computer equipment. SATIN and SCC planners were so confident that the last three Air Defense Sectors scheduled for SAGE installation, including my own Albuquerque, were delayed under the assumption that the improved SATIN/SCC equipment should be installed instead of the soon-obsolete original system. SCC cost estimates ballooned, and the program's ambitions were reduced month by month until it was canceled entirely in 1960. Albuquerque never got a SAGE installation, and the Albuquerque air defense sector was eliminated by reorganization later in 1960 anyway. Flight Service Stations Remember those Flight Service Stations, the ones that were originally built by the Post Office? One of the oddities of ATC is that they never went away. FSS were transferred to the CAB, to the CAA, and then to the FAA. During the 1930s and 1940s many more were built, expanding coverage across much of the country. Throughout the development of ATC, the FSS remained responsible for non-control functions like weather briefing and flight plan management. Because aircraft operating under instrument flight rules must closely comply with ATC, the involvement of FSS in IFR flights is very limited, and FSS mostly serve VFR traffic. As ATC became common, the FSS gained a new and somewhat odd role: playing go-between for ATC. FSS were more numerous and often located in sparser areas between cities (while ATC facilities tended to be in cities), so especially in the mid-century, pilots were more likely to be able to reach an FSS than ATC. It was, for a time, routine for FSS to relay instructions between pilots and controllers. This is still done today, although improved communications have made the need much less common. As weather dissemination improved (another topic for a future post), FSS gained access to extensive weather conditions and forecasting information from the Weather Service. This connectivity is bidirectional; during the midcentury FSS not only received weather forecasts by teletype but transmitted pilot reports of weather conditions back to the Weather Service. Today these communications have, of course, been computerized, although the legacy teletype format doggedly persists. There has always been an odd schism between the FSS and ATC: they are operated by different departments, out of different facilities, with different functions and operating practices. In 2005, the FAA cut costs by privatizing the FSS function entirely. Flight service is now operated by Leidos, one of the largest government contractors. All FSS operations have been centralized to one facility that communicates via remote radio sites. While flight service is still available, increasing automation has made the stations far less important, and the general perception is that flight service is in its last years. Last I looked, Leidos was not hiring for flight service and the expectation was that they would never hire again, retiring the service along with its staff. Flight service does maintain one of my favorite internet phenomenon, the phone number domain name: 1800wxbrief.com. One of the odd manifestations of the FSS/ATC schism and the FAA's very partial privatization is that Leidos maintains an online aviation weather portal that is separate from, and competes with, the Weather Service's aviationweather.gov. Since Flight Service traditionally has the responsibility for weather briefings, it is honestly unclear to what extend Leidos vs. the National Weather Service should be investing in aviation weather information services. For its part, the FAA seems to consider aviationweather.gov the official source, while it pays for 1800wxbrief.com. There's also weathercams.faa.gov, which duplicates a very large portion (maybe all?) of the weather information on Leidos's portal and some of the NWS's. It's just one of those things. Or three of those things, rather. Speaking of duplication due to poor planning... The National Airspace System Left in the lurch by the Air Force, the FAA launched its own program for ATC automation. While the Air Force was deploying SAGE, the FAA had mostly been waiting, and various ARTCCs had adopted a hodgepodge of methods ranging from one-off computer systems to completely paper-based tracking. By 1960 radar was ubiquitous, but different radar systems were used at different facilities, and correlation between radar contacts and flight plans was completely manual. The FAA needed something better, and with growing congressional support for ATC modernization, they had the money to fund what they called National Airspace System En Route Stage A. Further bolstering historical confusion between SAGE and ATC, the FAA decided on a practical, if ironic, solution: buy their own SAGE. In an upcoming article, we'll learn about the FAA's first fully integrated computerized air traffic control system. While the failed detour through SATIN delayed the development of this system, the nearly decade-long delay between the design of SAGE and the FAA's contract allowed significant technical improvements. This "New SAGE," while directly based on SAGE at a functional level, used later off-the-shelf computer equipment including the IBM System/360, giving it far more resemblance to our modern world of computing than SAGE with its enormous, bespoke AN/FSQ-7. And we're still dealing with the consequences today! [1] It also laid the groundwork for the consolidation of the industry, with a 1930 decision that took air mail contracts away from most of the smaller companies and awarded them instead to the precursors of United, TWA, and American Airlines.

4 days ago 8 votes
Sierpiński triangle? In my bitwise AND?

Exploring a peculiar bit-twiddling hack at the intersection of 1980s geek sensibilities.

5 days ago 8 votes