A collection of primarily development-related posts, about issues that I encounter and how I fixed them. Hopefully should be helpful for other developers!
  • Using Carthage in a Catalyst app

    “The process starts by checking a single box in Xcode” and then continues with compile errors. At least if your iOS app is using Carthage to manage its dependencies. Here my notes from working through those in my projects that use Carthage.

    1.) Get Carthage to compile Catalyst frameworks

    As of 21 October 2019, Carthage doesn’t yet support building specifically for macCatalyst nor does it support building xcframeworks (which would bundle a macCatalyst framework along with an iOS framework in the same meta framework). These features are coming up as part of Carthage’s Q4 2019 - Q1 2020 roadmap, but in the meantime you’ll have to use forks or PR that add those features. A simple way to start is to use the fork from Glenn L. Austin which adds --platform macCatalyst option to Carthage:
    1. Clone or download the fork of the Carthage repository
    2. Compile and install locally: make installWarning: If you provide your admin password in the last step, you might overwrite your existing stable release of the carthage command. Instead you can call sudo cp -f .build/release/carthage /usr/local/bin/carthage-catalyst to add it as a new carthage-catalyst command.
    Now you’re set and you can use carthage-catalyst update --platform macCatalyst to start building your dependencies for Catalyst. If that goes well, you can go straight to 3.) below, otherwise:

    2.) Address Carthage issues

    Firstly, if you have any Carthage compilation issues, update your depencies to the latest version as some projects have since added explicit support for Catalyst or might have since removed the use of deprecated APIs – many of which are unavailable in Catalyst. As you’re working through these issues you’ll go through this:
    • While carthage-catalyst build --platform macCatalyst --no-use-binaries --cache-builds fails
      • If updated branch exists for that library that adds support for Catalyst
        • Update your Cartfile
        • Run carthage-catalyst update --platform macCatalyst --no-use-binaries --cache-builds
      • Else
        • Fix up the project manually in Carthage/Checkouts
    Here’s the manual fixes that I had to apply to different projects: Carthage raises Fatal error: No SDKs found for scheme X If this happens, do the following:
    1. In the project configuration, check if “Base SDK” is set to “iOS”, if it’s not, set it to that.
    2. If it still fails, add “macosx” to “Supported Platforms”
    3. If it still fails, check if “Valid Architectures” overrides the default and if so, remove that override.
    That should then make Carthage itself happy, though xcodebuild might not: Compile issues
    • API not available: Your iOS library might be using deprecated classes that might still be available but have not been added to Catalyst. UIWebView in particular could be an offender here. Remove those uses.
    • Linked framework issue. If the library itself has dependencies that are managed using Carthage, you also need to update “Framework Search Paths”. In particular, you need to add new special cases for “Any macOS SDK” adding $(PROJECT_DIR)/Carthage/Build/macCatalyst and making sure it doesn’t use the Build/iOS directory.

    Libraries that didn’t work out-of-the-box:
    • Kingfisher: Set “Base SDK” to “iOS”
    • KVNProgress: 1.) Add “macosx” to “Supported Platforms” + Delete overwrite of “Valid Architectures”; 2.) Delete as it depends on GLKit which Mac Catalyst doesn’t seem to have.
    • RxSwift: Point at “master” branch (5.0.1) + set “Base SDK” to “iOS”
    • RxDataSources: Add “macosx” to “Supported Platforms” + fix up “Framework Search Paths”
    • UserVoice: Removed as it’s deprecated and relies heavily on UIWebView

    3.) Use it in your app

    To use the Catalyst frameworks in your iOS app, you’ll need to adjust your build settings as follows – the first step isn’t necessary but makes the other steps easier: 1) Add a “User-Defined Setting” and call it CARTHAGE_SUBFOLDER. It should default to “iOS”, and be overwritten as “macCatalyst” for “Any macOS SDK”:
    Add a User-Defined Setting
    Add a User-Defined Setting
    2) Change your “Framework Search Paths” to use $(PROJECT_DIR)/Carthage/Build/$(CARTHAGE_SUBFOLDER) rather than hard-coding the “iOS” subfolder, so that your builds are using the correct frameworks. 3) Adjust your carthage copy-frameworks build phase, to use different input and output file lists depending on whether you build for iOS or Mac Catalyst. You can do this by using $(SRCROOT)/carthage_input_$(CARTHAGE_SUBFOLDER).xcfilelist for the Input File Lists and $(SRCROOT)/carthage_output_$(CARTHAGE_SUBFOLDER).xcfilelist for the Output File Lists. Your carthage_input_iOS.xcfilelist and ``carthage_output_iOS.xcfilelist should list all your Carthage frameworks and their target paths as normal, however your the carthage_input_macCatalyst.xcfilelist and carthage_output_macCatalyst.xcfilelist` should be empty. Instead, you’ll embed the Catalyst frameworks as follows: 4) Add a new “Copy Files Phase” step, and add the Carthage frameworks there, setting them to “macOS” and ticking the “Code Sign On Copy” option. Now things should be working. If they don’t, it’s possibly Xcode’s fault. Try nuking your “Derived Data” folders might help. If you’re using fastlane a quick fastlane action clear_derived_data might do the trick.

    4.) Archive and distribute

    With the above steps doing an Archive build should already work. If you get an error like MyFramework.framework: bundle format is ambiguous (could be app or framework), then you likely skipped step 3 above. I always got this when trying to use the carthage copy-frameworks build phase, even when pointing these at $(SRCROOT)/Carthage/Build/macCatalyst/MyFramework. Follow my step 3 and 4 above to address this, as the Carthage README states this step isn’t necessary for macOS. However, I couldn’t confirm yet if this works for App Store builds as I’ve only distributed manually by signing using my Developer ID. Next, if you try to distribute or notarize your achive, you might get a crash in Xcode prior to 11.2, or an error in 11.2. This (likely) means, you don’t yet have a distribution certificate of type “Developer ID Application”. Create one on the Apple Developer portal.

    Next steps

    I hope all of this will get easier once Carthage supports XCFrameworks! Now that your app is running on macOS, off to refine it and make it feel more like a Mac app than an iOS app in a Mac window:

    Changelog:

    • 10 November: Updated 3.) with specific instructions of a more robust case.
    • 9 November: Added section ‘4.) Archive and distribute’. Removed note on KeychainAccess and OAuthSwift as those are Catalyst compatible as of the latest release.
    more…

  • iOS 13 toe-dipping: Multi-window support on iPad

    The code samples from Apple’s WWDC sessions and the sample code for supporting multiple windows, are a bit inconsistent which can make it a bit bumpy to get started. Here is the setup for supporting multiple windows on iPad that worked will in my experimentations so far more…

  • Keyboard shortcuts and the iOS responder chain

    When adding support for keyboard shortcuts, a seasoned iOS developer might raise an eyebrow when noticing that UIKeyCommand takes a selector for an action, but no target. This is because the keyboard commands1 are not send directly to any of your classes, but they instead are passed along the responder chain

    1. More specifically the selectors that fire when the keyboard commands are pressed. 
    more…

  • Good Practices for RxSwift-based View Models

    Everyone has their own preferred way of structuring their app code. I have grown to like view models and RxSwift. I am not trying to convince anyone to use it with this post, but rather want to share my best practices that took me a few years of using RxSwift to settle on more…

  • Getting push notifications for fastlane errors

    Fastlane is an excellent and essential tool for app developers to automate many of the otherwise time-consuming, tedious tasks. With a simple terminal command you can make run all the steps from compiling and packaging an app, to uploading it to the app stores, updating release notes, uploading the change log on a web page, to basically whatever else you can imagine. These tasks can take a while to run, so you want to know early when they fail. more…

  • Sharing iCloud Drive documents from your app

    I’m building a document-based iOS app for which I want to add sharing and collaboration features. Where possible, I try to use native iOS frameworks and features. When it comes to files, this means using iCloud Drive. This made me investigate how to build sharing and collaboration features on top of iCloud Drive. more…

  • RxSwift in custom views, the clean way

    RxSwift let’s you update your views dynamically in your app in many ways – many of which can get messy over time, as I’ve learned the hard way. The approach that I got used to in order to update views dynamically (and without resorting to crash-prone key-value-observation) was to pass Observables to the views. The views then subscribed to them and cleaning up o their own using a DisposeBag. It worked, but closely coupled the views to RxSwift. RxSwift has a much cleaner way for doing this – a Binder. more…

  • Importing app module in unit test fails to compile

    For months, CI and Xcode unit tests for a project at work have been plaguing me with a strange issue: At times and without obvious cause, some unit tests started failing to compile, saying that the classes of the app’s module could not get found – lots of Use of undeclared type and Use of unresolved identifier errors. more…

  • Lessons from Converting Complex Submodules to Cocoapods

    I have recently converted two fairly large iOS libraries (written in a mixture of Objective-C and Swift) from a plain git submodule to a set of dynamic frameworks that can be used as Cocoapods. The process was quite tedious, so here’s a random collection of hurdles that had to be crossed. more…

  • Upload Rejected Due to CFBundleResourceSpecification

    When submitting a new build to the App Store today, iTunes Connect greated me with a error that the build was rejected due to the use of CFBundleResourceSpecification in the Info.plist file. more…

  • Custom Transitions For Pushing Container View Controllers

    A common user interface paradigm in TripGo is to provide a list view with an associated map view. On the iPhone we display them on top of each other, while on the iPad we display them next to each other, which we have achieved by using separate storyboards for both devices. However, maintaining two storyboards has a number of downside and with iOS 9 around the corner, that adds the ability to show apps side-by-side, it is time to kiss goodbye to this approach. After briefly investigating the now more powerful UISplitViewController, we are now saying “Hello” to the magic world of trait collections, custom transitions and container views! more…

  • Drawing Multi-coloured Lines on an MKMapView

    MKMapView is a great toolkit to customise and add a variety of things to a map on iOS, but one thing that I found lacking was an easy way to draw a multi-coloured line. Specifically, I wanted a way to draw a border around a line but the standard MKPolylineView component does not allow doing that. As digging around on GitHub, Google and StackOverflow did not yield any existing projects that allow doing so, I have a look for how to do it and the result is ASPolylineView - a simple drop-in replacement for an MKPolylineView. more…

  • iOS, AddressBook Framework and GCD

    For my latest iOS app I was working on an autocompletion field that hooks into the user’s phone address book and calendar. This is a great way for making the user feel at home in your app and makes your app feel part of the phone. At first I tapped into the AddressBook and EventKit frameworks on the main thread while the user is typing, but that lead to a rather unresponsive user interface since both the AddressBook and EventKit frameworks can take a short while to access the contents. So, that work should be done on a background thread, while keeping the main thread available to respond to the user’s typing. more…