12 Mar 2016

Building an iOS share extension programmatically in Swift

I've been learning Swift recently, so I could use it for Larder's iOS app. After only two years or so working with Objective-C, I still have a lot to learn, but I got the hang of Swift enough to fumble my way through getting the Larder app started.

After a very simple table view set-up, I started working on the share extension, which will be the most useful part of Larder for iOS (for me, anyway). Eventually, the share extension will take a URL (maybe a title too), and let the user add a title and tags, as well as choosing which of their existing folders the link should be saved to.

I build all my layouts in code, rather than with Storyboard or xibs. There are reasons for and against this approach, and I know not many people do it. In the past it's often been necessary because of how specifically custom (and dynamic) a lot of the views in Exist for iOS are. But now it's just the way I like doing things.

This caused me a few headaches when working on the share extension. It seems very rare to find a tutorial about share extensions that uses a custom layout, and even more rare (I didn't find a single one) to find one using completely hand-coded views.

I found a couple of tutorials and GitHub projects that helped me get the initial set-up done, but getting anything to show up on screen when I tried to open the share extension seemed impossible.

Eventually I did figure it out. I now have a yellow rectangle showing up over Safari when I run the share extension in the simulator. This obviously isn't the end goal, but it was very tricky to figure out how to get this far, and actually putting together the view itself shouldn't be so hard.

Here are some of the main issues I ran into, and the fixes I found for them.


The second, longer answer from this Stack Overflow question has a few useful pointers. I was getting a crash every time I tried to launch the extension with the following error:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** setObjectForKey: object cannot be nil (key: <__NSConcreteUUID 0x174027280> 5AFB07AB-5DCD-46FE-8D07-44DE0F3789F2)'

This Stack Overflow answer helped me figure out that I needed to add @objc (TodayViewController) below the imports in my TodayViewController file (the custom class for my share extension's view controller—mine's actually called BCShareViewController).

I still don't actually know what this does. (Googling only turned up @objc protocols in the Apple docs, but I did notice a few mentions on Stack Overflow of other hoops to jump through when working with share extensions in Swift, so I expect it's to do with some of the share extension code on Apple's end being in Objective-C still.) Anyway, it stopped the crash I was getting every time and made my view show up finally!

Add NSExtensionPrincipalClass to Plist

This blog post helped me figure out that I had to add the key NSExtensionPrincipalClass to my plist. I'd also seen a suggestion on Stack Overflow that if using Swift I had to put Module.customClassName for this key, rather than just customClassName, but after adding the @objc line I talk about above, just using customClassName worked fine.

Sizing the view

Once I finally got my share extension's view showing up, it was full screen no matter what I did.

The only way around this I could find was this suggestion on Stack Overflow to make a view controller with another view controller inside it. The initial view controller is supposed to be hidden, with the smaller view controller inside it being visible to the user.

I couldn't get my initial view controller to be hidden without hiding the child/sub-view controller as well, so I just made the background colour of my initial view controller UIColor clearColor. It's normal to not be able to dismiss a share extension by tapping on the background—they generally have a cancel button in the top left of the view that you need to tap. Thankfully, this means my workaround of having a full screen clear view controller isn't so bad.

share extension

Here's what the code for my share extension's view controller looks like now:

import UIKit
import Social

@objc (BCShareViewController)

class BCShareViewController: UIViewController {
	override func viewDidLoad() {
	override func loadView() {
	func setUpView() {
		let viewFrame = CGRect(x: 0, y: 0, width: 150, height: 300)
		self.view = UIView(frame: viewFrame)
		self.view.backgroundColor = UIColor.clearColor()
		let width: CGFloat = UIScreen.mainScreen().bounds.size.width
		let height: CGFloat = UIScreen.mainScreen().bounds.size.height
		let newView = UIView(frame: CGRect(x: (width * 0.10), y: (height * 0.25), width: (width * 0.75), height: (height / 2)))
		newView.backgroundColor = UIColor.yellowColor()