Writing an iOS 8 Share Extension Without a Storyboard

Writing an iOS 8 share extension without a storyboard

Figured it out!

Turns out there's a weird module naming thing going on in Swift, so you can fix it by adding an @objc name to the class:

@objc(PrincipalClassName)

class PrincipalClassName: UIViewController {
...

and then set the NSExtensionPrincipalClass key to PrincipalClassName.

iOS App Extension with xib instead of storyboard

all you need to do is add "NSExtensionPrincipalClass" and the classname which you want.

Use storyboard in share extension

Most likely your view controller is still a subclass of SLComposeServiceViewController. That class will always display the default share UI if you use it. If you don't want that UI, you should change your view controller to inherit from UIViewController instead.

How to create a Today widget programmatically without storyboard on iOS8?

Remove NSExtensionMainStoryboard from Info.plist
Add NSExtensionPrincipalClass = YourViewController

Don't forget to create your own view in loadView

iOS Share Extension with custom View Controller

These general steps worked for me without using SLComposeServiceViewController (here's the code at the commit when it was implemented). The image at the end shows our result, but step 6 can be anything, not just a form.

Steps:

  1. (code) Change ShareViewController to simple UIViewController

  2. (code) Add blur effect to ShareViewController

  3. (storyboard) Add container view to ShareViewController

  4. (storyboard) Add navigation controller

  5. (storyboard) Embed navigation controller in ShareViewController's container view

  6. Customize the view controllers in the navigation controller (see this SO thread for example)


Step 1. Change ShareViewController to simple UIViewController

import UIKit

class ShareViewController: UIViewController {
// ^^^^^^^^^^^^^^^^

Step 2. Add blur effect to ShareViewController

    // ShareViewController continued from Step 1.

override func viewDidLoad() {
super.viewDidLoad()

// https://stackoverflow.com/questions/17041669/creating-a-blurring-overlay-view/25706250

// only apply the blur if the user hasn't disabled transparency effects
if UIAccessibilityIsReduceTransparencyEnabled() == false {
view.backgroundColor = .clear

let blurEffect = UIBlurEffect(style: .dark)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
//always fill the view
blurEffectView.frame = self.view.bounds
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

view.insertSubview(blurEffectView, at: 0)
} else {
view.backgroundColor = .black
}
// Do any additional setup after loading the view.
}

Step 3. Add container view to ShareViewController

Drag a Container View from the Object Library into the ShareViewController on the storyboard, and adjust dimension. For example:

Sample Image

Step 4. Add navigation controller

Drag a Navigation Controller from the Object Library to the storyboard.

Step 5. Embed navigation controller in ShareViewController's container view

Control-drag from the container view of ShareViewController to the navigation controller, select "Embed" from the menu. Should look similar to this:

Sample Image

Step 6. Customize the view controllers in the navigation controller (see this SO thread for example)

My result:

Sample Image

Is it possible to open full view controller in Share extension, instead of popup (default)

Step1: Add new UIViewController named MainPageViewController.

Step2: Add new View Controller in MainInterface Storyboard, change it's class to MainPageViewController in Custom Class section of Storyboard, set Storyboard ID to MainPageViewController in Identity section.

Step3: Open Share extension's info.plist

By default it looks something like this-

<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<string>TRUEPREDICATE</string>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>

Now instead of the default NSExtensionMainStoryboard key use NSExtensionPrincipalClass key, so the final result will be

<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<string>TRUEPREDICATE</string>
</dict>
<key>NSExtensionPrincipalClass</key>
<string>HomeViewController</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>

Where HomeViewController is our new entry point controller.

Step4: Now, the most important we need to fix weird issue related with module naming.

To fix it we need to add @objc(HomeViewController) at the top of your HomeViewController file.

Step5: Also to animate the presentation, we need to add animation code in viewWillAppear

See below reference code:

import UIKit

@objc(HomeViewController)

class HomeViewController : UINavigationController {

init() {
let viewController:UIViewController = UIStoryboard(name: "MainInterface", bundle: nil).instantiateViewController(withIdentifier: "MainPageViewController") as UIViewController
super.init(rootViewController: viewController)
}

required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

self.view.transform = CGAffineTransform(translationX: 0, y: self.view.frame.size.height)

UIView.animate(withDuration: 0.25, animations: { () -> Void in
self.view.transform = CGAffineTransform.identity
})
}
}

Where MainPageViewController is the identifier of your MainViewController, which we want to present.

Step6: To dismiss with animation we can create new function in MainPageViewController class:

func hideExtensionWithCompletionHandler(completion:@escaping (Bool) -> Void) {
UIView.animate(withDuration: 0.20, animations: {

self.navigationController!.view.transform = CGAffineTransform(translationX: 0, y: self.navigationController!.view.frame.size.height)
}, completion: completion)
}

call above func on Save or Cancel button and inside block, we can call completeRequest or cancelRequest(withError:)

func saveButtonTapped(sender: UIBarButtonItem) {
self.hideExtensionWithCompletionHandler(completion: { (Bool) -> Void in
self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil)
})
}

Now, Do whatever you want to do man, Do double man ;-)



Related Topics



Leave a reply



Submit