Using custom annotation views in MKMapView

If you want to display completely custom views as “pins” on the map in your iOS application, you should use annotations. All your data needs to be represented as objects conforming to the MKAnnotation protocol, with title, subtitle and coordinate as the required properties.

Custom view implementation

Visually you represent an MKAnnotation with a MKAnnotationView. You can create a custom class that subclasses MKAnnotationView and implement your custom UI in that class.

Here is an sample MKAnnotationView with fixed size that displays just one custom view

final class LocationAnnotationView: MKAnnotationView, Reusable {

    // MARK: Initialization

    override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)

        frame = CGRect(x: 0, y: 0, width: 40, height: 50)
        centerOffset = CGPoint(x: 0, y: -frame.size.height / 2)

        canShowCallout = true

    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")

    // MARK: Setup

    private func setupUI() {
        backgroundColor = .clear

        let view = MapPinView()

        view.frame = bounds

The MKAnnotationView is by default aligned to its corresponding position on map with the bottom left corner. If your MKAnnotationView looks like a pin for example, you need to align it to the position on the map with the bottom center point. To do that you use the centerOffset property as shown.

Registering the custom view with MKMapView

The next step is to tell MKMapView to user your custom class.

Using single custom MKAnnotationView

If you want to display only one type of annotation in your MKMapView, just register your custom class with the MKMapViewDefaultAnnotationViewReuseIdentifier reuse identifier

mapView.register(LocationAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)

This is enough to make MKMapView to completely handle creating and recycling instances of your custom MKAnnotationViews for you. No need to implement any MKMapView delegate methods for providing annotation views.

[Read More]
iOS  Xcode  MapKit 

Logging error messages from assert and fatalerror

I often use fatalerror(message:) in my code base to deal with invalid states when the application cannot continue. A typical example can be a method that requires to be called only after the user has logged in:

guard let loggedUser = dataStore.user else {
	fatalerror("Invalid use before signup is complete")

The problem is that the fatalerror message does not appear in the crash log. You can of course take a look at the whole stack trace to figure out where the fatalerror originated but seeing the message in the logs yout get from your uses immediately would be much better.

I use PLCrashReporter to store crash logs locally so users can export them from the application together with all the logs.

I tried logging the message every time before calling fatalerror

guard let loggedUser = dataStore.user else {
	Log.error?.message("Invalid use before signup is complete")
	fatalerror("Invalid use before signup is complete")

but this is really not ideal, it is just writing boilerplate code you can easily forget.

I have not found a way to directly log the fatalerror message, so I created my own fail method

func fail(_ logMessage: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) {
    let formattedMessage = formatLogMessage(logMessage, file: file, function: function, line: line)
    fatalError(formattedMessage, file: file, line: line)

You can format the message you log any way you want, I just log the filename, function name and line number. Getting the filename from a StaticString is a bit tricky though

func formatLogMessage(_ logString: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) -> String {
    let filename = (file.withUTF8Buffer {
        String(decoding: $0, as: UTF8.self)
    } as NSString).lastPathComponent
    return "[\(filename):\(line) \(function)]: \(logString)"

Instead of calling fatalerror(message:) I now call fail(message:) instead in all the places it is needed and the message is always logged.

As a downside if you have debugger attached it stops in the actual fatalerror call not on the fail method call, so you need to move one method up in the stack trace to see the actual place your application failed.

For me it is worth it, I am much more interested in the error messages in the logs than this.

In my code base I also define a failDebug(message:) method with the same code just replacing fatalerror(message:) with assertionFailure(message:).

[Read More]
iOS  Xcode 

Workaround for Swift scripts crashing after update to Xcode 11.4

If you have already updated to Xcode 11.4 that ships with Swift 5.2 you might have noticed that your Swift scripts all started crashing when accessing anything related to URL.

I use a script to generate a list of libraries used in the app and their licenses and running it now results in

Stack dump:
0.	Program arguments: /Applications/ -frontend -interpret test.swift -enable-objc-interop -stack-check -sdk /Applications/ -color-diagnostics -module-name test
1.	Apple Swift version 5.2 (swiftlang-1103.0.32.1 clang-1103.0.32.29)
2.	While running user code "fetch_licenses.swift"
0  swift                    0x000000010e62d4ea PrintStackTraceSignalHandler(void*) + 42
1  swift                    0x000000010e62ccc0 SignalHandler(int) + 352
2  libsystem_platform.dylib 0x00007fff71c8242d _sigtramp + 29
3  libsystem_platform.dylib 0x0000000000004936 _sigtramp + 2386044198
4  libsystem_platform.dylib 0x00000001172ce020 _sigtramp + 2774842384
5  swift                    0x000000010a3b09ba llvm::MCJIT::runFunction(llvm::Function*, llvm::ArrayRef<llvm::GenericValue>) + 458
6  swift                    0x000000010a3b7a2b llvm::ExecutionEngine::runFunctionAsMain(llvm::Function*, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, char const* const*) + 2011
7  swift                    0x000000010a38caea performCompileStepsPostSILGen(swift::CompilerInstance&, swift::CompilerInvocation&, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule> >, bool, llvm::PointerUnion<swift::ModuleDecl*, swift::SourceFile*>, swift::PrimarySpecificPaths const&, bool, int&, swift::FrontendObserver*, swift::UnifiedStatsReporter*) + 14362
8  swift                    0x000000010a3814a5 swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 55813
9  swift                    0x000000010a2f74d3 main + 1283
10 libdyld.dylib            0x00007fff71a847fd start + 1
11 libdyld.dylib            0x000000000000000b start + 2388113423
fish: '/usr/bin/env xcrun swift fetch_…' terminated by signal SIGSEGV (Address boundary error)

This is a Swift bug that has been already reported.

Luckily I found a workaround!.

If you do not run the script directly with swift but instead compile it first with swiftc and then run the binary, everything works just fine.

Update: The problem is caused by having other tools that bundle Swift libraries installed on the system, like swiftlint and uninstalling them solves the crash.

macOS  Xcode 

Determining which frameworks use UIWebView

Apple has deprecated UIWebView and will not be accepting new apps with UIWebView starting April 2020 and app updates with UIWebView starting December 2020. If your app uses UIWebView you should replace it with WKWebView.

The check for UIWebView has already been implemented as a warning after a build submission:

ITMS-90809: Deprecated API Usage - Apple will stop accepting submissions of app updates that use UIWebView APIs starting from December 2020. See for more information.

Removing UIWebView from your app might seem quite straightforward, but you probably use some 3rd party libraries and they might also contain UIWebView. You need to find all of them and update them, if available, or replace them. This process is not exactly trivial.

If you use 3rd party libraries as code, for example via Cocoapods, you can just do a text search for UIWebView in their sources.

For example doing grep -r 'UIWebView' . in RxSwift sources prior to 5.1.0 gives you a lot of results as RxSwift has included UIWebView binding before 5.1.0.

❯ grep -r 'UIWebView' .
./RxCocoa/iOS/UIWebView+Rx.swift://  UIWebView+Rx.swift
./RxCocoa/iOS/UIWebView+Rx.swift:    extension Reactive where Base: UIWebView {
./RxCocoa/iOS/UIWebView+Rx.swift:        public var delegate: DelegateProxy<UIWebView, UIWebViewDelegate> {
./RxCocoa/iOS/UIWebView+Rx.swift:                .methodInvoked(#selector(UIWebViewDelegate.webViewDidStartLoad(_:)))
./RxCocoa/iOS/UIWebView+Rx.swift:                .methodInvoked(#selector(UIWebViewDelegate.webViewDidFinishLoad(_:)))
./RxCocoa/iOS/UIWebView+Rx.swift:                .methodInvoked(#selector(UIWebViewDelegate.webView(_:didFailLoadWithError:)))
./RxCocoa/iOS/Proxies/RxWebViewDelegateProxy.swift:extension UIWebView: HasDelegate {
./RxCocoa/iOS/Proxies/RxWebViewDelegateProxy.swift:    public typealias Delegate = UIWebViewDelegate

If your 3rd party libraries come as a .framework file without sources, there is a different way to check for UIWebView.

You can use nm to get the symbols table of the executable in a .framework and do a grep there

❯ nm AWSDK.framework/AWSDK | grep -i UIWebView
                 U _OBJC_CLASS_$_UIWebView
                 U _OBJC_CLASS_$_UIWebView
0000000000002a00 S __OBJC_LABEL_PROTOCOL_$_UIWebViewDelegate
0000000000002998 D __OBJC_PROTOCOL_$_UIWebViewDelegate
0000000000002348 s l_OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_UIWebViewDelegate
00000000000023b0 s l_OBJC_$_PROTOCOL_METHOD_TYPES_UIWebViewDelegate
0000000000002330 s l_OBJC_$_PROTOCOL_REFS_UIWebViewDelegate
000000000000aaa0 S __OBJC_LABEL_PROTOCOL_$_UIWebViewDelegate
000000000000a858 D __OBJC_PROTOCOL_$_UIWebViewDelegate
0000000000005780 s l_OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_UIWebViewDelegate
00000000000057e8 s l_OBJC_$_PROTOCOL_METHOD_TYPES_UIWebViewDelegate
0000000000005768 s l_OBJC_$_PROTOCOL_REFS_UIWebViewDelegate

Faster way to download and install Xcode

As an iOS developer you need to periodically update your Xcode, on your own machine and on your CI/CD server if you automate your development workflow. There are multiple ways to do this so do not waste your time and use the fastest way possible.

Forget Mac App Store, use Apple Developer Portal

Installing Xcode from the Mac App Store might seem like a convenient way to do so but it is too slow and inflexible. You cannot use the Mac App Store to install multiple version of Xcode at the same time if you need them, like when testing with a Xcode beta for an upcoming iOS release. Download from the Mac App Store is incredibly slow and sometimes not even available for days after release (like 11.2.1).

The place to go is the Apple Developer Portal where you can find all the Xcode versions, including the betas.

Faster download with aria2

Downloading Xcode from the Apple Developer Portal is faster than using the Mac App Store, but it can be made even better. You just need to use the right tools.

Install aria2 from Homebrew and uses this Ruby script from Ian Dundas:

#!/usr/bin/env ruby

print "What is the URL of your Apple Downloads resource?\nURL:"
url = gets.strip

print "What is the ADCDownloadAuth cookie token:\nADCDownloadAuth: "
token = gets.strip

command = "aria2c --header \"Host:\" --header \"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\" --header \"Upgrade-Insecure-Requests: 1\" --header \"Cookie: ADCDownloadAuth=#{token}\" --header \"User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 10_1 like Mac OS X) AppleWebKit/602.2.14 (KHTML, like Gecko) Version/10.0 Mobile/14B72 Safari/602.1\" --header \"Accept-Language: en-us\" -x 16 -s 16 #{url} -d ~/Downloads"


This script downloads the given Xcode by URL from the Apple Developer Portal, but uses up to 16 separate connections to do so. You will see a significant download speed improvement.

Make sure you use the “More” site at even for downloading the latest version of Xcode.

When copying the ADCDownloadAuth cookie make sure you copy the correct value, Safari adds all kinds stuff around it when you just use “copy value”.

Faster install with xip and deleting previous Xcode first

When you install the Xcode xip file you need to extract it. You can double click it in Finder and wait or you can use

xip -x Xcode11.xip

Using xip from the command line is much faster because it does not verify the file signature like double clicking in Finder. Of course this is a potential security risk, so it is up to you to decide if it is worth it.

Another trick is not to drag the extracted to /Applications immediately but delete the existing /Applications/ first. I guess this is related to Finder first getting the list of those thousands of files in the before the update.

iOS  Swift  Xcode