システム開発部のTです。
前回につづいて、今回もShareExtensionのネタを書いていきたいと思います。
今回は、ShareExtensionの機能を利用するときにコールされるUIをカスタマイズするやり方を書いていきたいと思います。
UIのカスタマイズ自体、iOSのプログラマーにとっては簡単にできるからなのか、取り上げているサイトがあまり無かったように見受けられたので、ちょっと掘り下げていくように書いていければと思います。
なお、前回の記事で取り上げたコードを流用するので、こちらを初めて見る方は、一度前回の記事を参照いただければと思います。
ShareViewControllerのコード初期化
前回の記事からの続きになりますが、まずはShareViewControllerのコードを見ていきましょう。
プロジェクト作成直後だと、以下の内容で生成されているかと思います。
import UIKit
import Social
class ShareViewController: SLComposeServiceViewController {
override func isContentValid() -> Bool {
// Do validation of contentText and/or NSExtensionContext attachments here
return true
}
override func didSelectPost() {
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
override func configurationItems() -> [Any]! {
// To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
return []
}
}
上記を以下の内容に変更します。
//class ShareViewController: SLComposeServiceViewController {
class ShareViewController: UIViewController { // <- UIViewControllerに変更する
override func viewDidLoad() { // UI初期化のためのviewDidLoadを追加
}
// override func isContentValid() -> Bool { // 不要なので削除
// // Do validation of contentText and/or NSExtensionContext attachments here
// return true
// }
//
// override func didSelectPost() { // 不要なので削除
// // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
//
// // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
// self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
// }
//
// override func configurationItems() -> [Any]! { // 不要なので削除
// // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
// return []
// }
}
一旦、ShareViewControllerはここまで。
上記ではわかりやすいようにコメント化していますが、バッサリ削除してしまってもOKです。
以降では、いよいよUIのカスタマイズをしていきたいと思います。
MainInterface.storyboardをカスタマイズ
では次にUIをカスタマイズしていきます。
っていっても、普通に画面を生成するのとあまり変わりありません。
まずは、MainInterface.storyboardを開きます。

中身見てもらうとわかりますが、SafeAreaには何も入っていませんね・・・。
ではここに簡単なUIを構築していきたいと思います。
以下、簡単なUIを作ってみました。

共有されたデータを表示する領域、登録ボタン、キャンセルボタン・・・って感じです。
登録ボタンっていっても、本件では実際に登録するような処理はオミットします。
上記で定義した項目は、ShareViewControllerに反映してください。

ここまでで、ShareViewControllerの内容は以下のようになっているかと思います。
import UIKit
import Social
class ShareViewController: UIViewController {
@IBOutlet weak var shareTextLabel: UILabel!
override func viewDidLoad() {
}
@IBAction func onTapRegist(_ sender: Any) {
}
@IBAction func onTapCancel(_ sender: Any) {
}
}
ここまで実装したら、一旦アプリを実行してみましょう。
ここで気をつけてほしいのは、親プロジェクトをインストールしてから、ShareExtensionのサブプロジェクトを実行すること。
インストール後、Safariを起動し、共有ボタンを押下してください。
共有先にTestappExtensionsを選択すると、以下の「テストタイトル」画面が表示されるかと思います。


ここまでくれば、あとは処理を埋め込むだけですね。
とりあえず、ブラウザからURLが連携されるので、その処理を埋め込みましょう。
処理の実装
viewDidLoad()に共有された内容を取得するためのコードを入れます。
本件では、URLを取得するコードを入れました。
override func viewDidLoad() {
super.viewDidLoad()
guard
let item = self.extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = item.attachments?.first
else {
let error = NSError(domain: "Etc Error", code: 0, userInfo: [NSLocalizedDescriptionKey: "An error description"])
// cancelRequestで呼び出し元にエラー通知し、共有画面を終了する
self.extensionContext?.cancelRequest(withError: error)
return
}
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
itemProvider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) {[weak self] (url, error) in
if let url = url as? NSURL {
guard let urlString = url.absoluteString else {
let error = NSError(domain: "URL not found error", code: 0, userInfo: [NSLocalizedDescriptionKey: "An error description"])
self?.extensionContext?.cancelRequest(withError: error)
return
}
DispatchQueue.main.async {
self?.shareTextLabel.text = urlString
}
} else {
let error = NSError(domain: "URL not found error", code: 0, userInfo: [NSLocalizedDescriptionKey: "An error description"])
self?.extensionContext?.cancelRequest(withError: error)
return
}
}
}
}
URLが取得できなかったときのエラーハンドリングとして、以下のコードを埋め込んでいます。
self?.extensionContext?.cancelRequest(withError: error)
cancelRequestを実行すると、共有画面を終了し、呼び出し元にエラーを通知することができます。
そして、ボタン押下時の処理も入れていきます。
一応登録とキャンセルで2つ処理を分けていますが、本件では処理を同一にしました。
@IBAction func onTapRegist(_ sender: Any) {
// 共有画面を閉じる。returningItemsは呼び出し元に渡したい項目がある場合に設定する
self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
@IBAction func onTapCancel(_ sender: Any) {
self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
ボタン押下時の処理にて、以下を埋め込んでいます。
completeRequestは、共有画面を終了し、returningItemsに設定した内容を呼び出し元に通知することができます。
本件では、通知する内容は無いため、空配列を設定しています。
self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
ここまで実装して、最終的に以下の内容になっているかと思います。
import UIKit
import Social
import MobileCoreServices
class ShareViewController: UIViewController {
@IBOutlet weak var shareTextLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
guard
let item = self.extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = item.attachments?.first
else {
let error = NSError(domain: "Etc Error", code: 0, userInfo: [NSLocalizedDescriptionKey: "An error description"])
// cancelRequestで呼び出し元にエラー通知し、共有画面を終了する
self.extensionContext?.cancelRequest(withError: error)
return
}
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
itemProvider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) {[weak self] (url, error) in
if let url = url as? NSURL {
guard let urlString = url.absoluteString else {
let error = NSError(domain: "URL not found error", code: 0, userInfo: [NSLocalizedDescriptionKey: "An error description"])
self?.extensionContext?.cancelRequest(withError: error)
return
}
DispatchQueue.main.async {
self?.shareTextLabel.text = urlString
}
} else {
let error = NSError(domain: "URL not found error", code: 0, userInfo: [NSLocalizedDescriptionKey: "An error description"])
self?.extensionContext?.cancelRequest(withError: error)
return
}
}
}
}
@IBAction func onTapRegist(_ sender: Any) {
// 共有画面を閉じる。returningItemsは呼び出し元に渡したい項目がある場合に設定する
self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
@IBAction func onTapCancel(_ sender: Any) {
self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
}
ここまで来たら、再度ビルド&インストールして、動作確認してみましょう。
動作確認
ここまで、いかがだったでしょうか?
UIのカスタマイズも、通常のUIと同じように作れることが理解できたかと思います。
さいごに
業務で初めてShareExtensionを実装したときに、UIをカスタマイズする必要が出てきたとき、関連記事が少なかったため、勝手が分からず苦戦してしまいました。なので、本件を備忘録として残そうと思いました。同じようにお悩みの方にお役になることができれば幸いです。
これからも、ShareExtensionについてもう少し深堀りした内容を連携できればと思います。
本日はここまで!