7

I've tried creating a custom view controller for a share extension.

A confusing situation happens when I present another view controller on top of the initial view controller that was set on the MainInterface.storyboard. This presented view controller is embedded in a navigation controller (it's the root view controller of it).

I did a check on the presentingViewController

(lldb) po [self presentingViewController]

<_UIViewServiceViewControllerOperator: 0x7a978000>

(lldb) po [[self presentingViewController] extensionContext]

nil

So, the extension context is nil at this point. I could access the extensionContext by passing it around from the presentingViewController to the presentedViewController.

But, I found this behavior is a bit strange. Is the app extension designed to only be accessed from one level of view controller hierarchy?

Jesse Armand
  • 1,842
  • 3
  • 17
  • 26

3 Answers3

6

If you're going to use more than a single view controller in your extension storyboard, you'll have to pass a reference to the extensionContext of the original view controller to the view controller that will ultimately be responsible for completing the extension's request. In the initial view controller:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    let destination = segue.destinationViewController as! FinalViewController
    destination.originalExtensionContext = self.extensionContext
}

And in your final view controller:

@IBAction func dismissController(sender: UIButton!) {
    dismissViewControllerAnimated(true) { () -> Void in
        self.originalExtensionContext.completeRequestReturningItems(self.originalExtensionContext.inputItems, completionHandler: nil)
}

Note that you have to create a uniquely named property for the original extension context, since extensionContext already exists as a property name on the superclass UIViewController. You can't pass the existing extensionContext to the UIViewController's property extensionContext as it is a read-only attribute.

Michael Frain
  • 76
  • 1
  • 2
  • Yup, I have figured that this is the most obvious way to pass around extension context. I was asking about the behavior of extensionContext property in the SDK. – Jesse Armand Nov 17 '15 at 03:41
  • As far as I can tell (and I've used this pattern a couple of times now), there's no unusual behavior if you pass a reference to `extensionContext`. You just need to make sure that the last thing you do is call `completeRequestReturningItems:` since that will deallocate the extension context and anything called within the extension after that won't do anything. – Michael Frain Nov 17 '15 at 17:55
1

The view controller being presented by a view controller should have no problem using the parent's extension. Taking a look at the documentation:

The view controller can check this property to see if it participates in an extension request. If no extension context is set for the current view controller, the system walks up the view controller hierarchy to find a parent view controller that has a non nil extensionContext value.

Therefore, if you can be certain of the fact that your root view controller does indeed have an extensionContext, any view controller presented by this view controller should have access to it, simply through it's own extensionContext property.

Note: If this is not the behaviour you a re observing, this may be a bug with the SDK, and I would recommend filing a radar.

Infinity James
  • 4,667
  • 5
  • 23
  • 36
  • 7
    It behaves correctly if the view controller has a parent that has an extensionContext, but it behaves differently when it's a presentedViewController, which is not included in a parent - child relationship. – Jesse Armand Sep 03 '14 at 08:53
  • 1
    @JesseArmand is correct. Any view presented modally will not contain it's presenting view controllers extensionContext. – Dan Loewenherz Sep 21 '14 at 16:05
1

While it's not the best approach for clean code and architecture, it's quite handy:

In root extension controller where extensionContext exists:

final class ShareRootViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        NSExtensionContext.shared = self.extensionContext
    }
}

extension NSExtensionContext {
    fileprivate(set) static var shared: NSExtensionContext!
}

In any other view controller:

let context = NSExtensionContext.shared