Development Quickies
In diesem Artikel findet sich eine Sammlung diverser Tips und Tricks zum Thema macOS-/iOS-Softwareentwicklung mit Xcode, die im Alltag immer wieder nützlich sein können. Falls bereits aus einem dieser Quickies ein eigener Artikel entstand, ist dieser jeweils am Ende der Beschreibung verlinkt.
Xcode
validate .strings files
The following terminal command only indicates whether or not the file is syntactically valid:
plutil -lint /path/to/Localizable.strings
Batch check of all .strings files within the current working directory:
find . -name \*.strings -exec plutil -lint {} +
app extensions
Things learned while writing WidgetKit app extensions
- Multiple instances (sizes) of the same widget ...
- ... apparently run within the same runtime environment so avoid writing data from the configuration intent to some kind of singleton or static class since it will be overwritten by a second instance with a different configuration. But on the other hand don't rely on this data on being shared across widget instances. This seems to be an undocumented bejavior and may not always apply, and it might change in the future.
- Self-Updating widgets/High frequency updates
- Some people suggest you should reload the timeline from within getTimeline() by calling WidgetCenter.shared.reloadAllTimelines() in order to achieve a higher update frequency of your extension. But doing this will drive up the cpu load, inevitably drain your battery and shorten the devices' life span in the process. So better avoid such dirty hacks and stick to the original plan for updating your widgets as laid out in Apple's documentation. Get used to the fact that high frequency updates of WidgetKit app extensions are simply not possible any more. Rather file a complaint with Apple, maybe they listen sooner or later and provide a proper mechanism for this specific use case.
- When your extensions write shared data to a shared resource
- Make use of semaphores when writing shared data to a shared resource such as a keychain or a user defaults store. You may run into race conditions when multiple instances of the same app extension can process or show different data, especially when they are configurable (i.e. have a configuration intent). They may have to write something in there simultaneously as they might receive an update trigger at the very same time and write operations on a shared keychain aren't always blazingly fast.
check if we're running within an extension
- at compile-time
Objective-C
#ifdef TARGET_IS_EXTENSION // this piece of code will only be compiled when building for app extension targets #endif
- at runtime
Objective-C
if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) { // this is an app extension }
Swift
- or
if Bundle.main.bundlePath.hasSuffix(".appex") { // this is an app extension }
if Bundle.main.bundleURL.pathExtension == "appex" { // this is an app extension }
Things to keep in mind
Foundation Framework
NSArray:indexOfObject:
returns 0 and NOT NSNotFound
as you might have expected when the array is nil, so you better check that first:
NSArray *array = nil;
NSInteger index = [array indexOfObject:@"test"];
Tested on macOS Version 12.6
Important Resources
Interfaces
Guidelines for Authentication Services
- Sign in with Apple: Overview, Technical Guidelines, Apple Design Resources
- Google: Sign-In Branding Guidelines
- Meta User Design Guidelines: Facebook Login Button, Facebook Logo