はじめに
AppExtensionでShareしたURLをCoreDataに保存しようとしたときにいろいろハマったのでメモを残しておきます。
環境
Xcode 6.3.1
swift 1.2
アプリのDeployment Targetは8.0
以上を想定しています。
検証はしていませんが、Embedded FrameworkがswiftだとiOS7でも動作するようなので、7.0
でも問題ないかもしれません。
やること
- プロジェクトの作成
- AppExtensionの作成
- Embedded Frameworkの作成
- App Groupsの設定
- CoreData周りの実装
- AppExtension周りの実装
1. プロジェクトの作成
Xcodeの[File] -> [New] -> [Project]から新しいプロジェクトを作成します。
今回はSingle View Application
でプロジェクトを作成はし、アプリ名をAppExtension_CoreData
としました。
Use CoreData
のチェックボックスのチェックを忘れずに入れておきます。
2. AppExtensionの作成
プロジェクト設定からApplication Extension
を追加します。
今回はShare Extension
を選択します。
Product NameはACShareExtension
としました。
3. Embedded Frameworkの作成
上と同様にプロジェクト設定からFramework & Libaray
を選択し、Cocoa Touch Framework
を追加します。
Product NameはACFramework
としました。
このEmbedded Frameworkはアプリ本体とエクステンション間でソースコードを共有するために使用します。
今回は主にCoreDataのハンドリング周りの処理をFrameworkに追加していきます。
4. App Groupsの設定
App Groupsはアプリ本体とエクステンション間でUserDefaultsやファイル、CoreDataなどを共有するための仕組みとなります。
プロジェクト設定のCapabilities
-> App Goups
スイッチをONにします。
+ボタンを押して、gourpの識別子を入力します。
この識別子はgroup.
から始まるように設定します。
識別子が作成されたらチェックボックスにチェックを入れておきます。
次に、AppExtensionのターゲットに移動し、App Groups
のスイッチをONにした後に、作成した識別子と同じもののチェックボックスにチェックを入れます。
5. CoreData周りの実装
CoreData Stackの移動
まずはAppDelegateに自動生成されるCoreData周りの処理をEmbedded Frameworkの方に全て移動します。
ACFrameworkにACCoreDataManager
という名前でクラスを作成しました。
このクラスはシングルトンクラスとして、sharedInstanceからCoreDataへアクセスするようにしています。
import UIKit
import CoreData
public class ACCoreDataMamager: NSObject {
// MARK: - Shared Manager
public class var sharedInstance : ACCoreDataMamager {
struct Static {
static let instance = ACCoreDataMamager()
}
return Static.instance
}
// MARK: - Core Data stack
lazy var applicationDocumentsDirectory: NSURL = {
....
Entityの作成
次に、データモデルを編集します。
AppExtension_CoreData.xcdatamodel
を選択し、エンティティを追加します。
今回はMemo
というエンティティにdate
とtitle
をもったシンプルなものにしました。
この際に、右カラムのTarget Membership
にエクステンションのターゲットにもチェックを入てください。
Entityのサブクラスを作成
データへのアクセスを楽にするために、先ほど作成したMemo
エンティティのサブクラスを作成します。
ACFrameworkにNSManagedObjectクラスのACMemoEntity
を作成します。
import UIKit
import CoreData
public class ACMemoEntity: NSManagedObject {
@NSManaged public var title : String
@NSManaged public var date : NSTimeInterval
}
作成を終えたら、AppExtension_CoreData.xcdatamodel
に戻って、Memo
エンティティの名前空間を登録しておきます。
くわしくはこちら↓
CoreDataのラッパークラスの作成
CoreDataのデータへのアクセスを楽にするために、ラッパークラスを作成します。
ACFrameworkにACDataManager
というのを作成し、Memo
エンティティへの追加と取得部分の実装を追加します。
import UIKit
import CoreData
public class ACDataManager: NSObject {
public static func createMemo(title : String, block : (Bool) -> Void) {
let context = ACCoreDataMamager.sharedInstance.managedObjectContext
let entity : NSEntityDescription = NSEntityDescription.entityForName("Memo", inManagedObjectContext: context!)!
var memo : ACMemoEntity = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context!) as! ACMemoEntity
memo.title = title
memo.date = NSDate().timeIntervalSince1970
ACCoreDataMamager.sharedInstance.saveContext(block)
}
public static func getMemo() -> Array<ACMemoEntity>{
let fetchRequest : NSFetchRequest = NSFetchRequest()
let context = ACCoreDataMamager.sharedInstance.managedObjectContext
let entity : NSEntityDescription = NSEntityDescription.entityForName("Memo", inManagedObjectContext: context!)!
fetchRequest.entity = entity
let sortDescriptor = NSSortDescriptor(key: "date", ascending: false)
let sortDescriptors = [sortDescriptor]
fetchRequest.sortDescriptors = sortDescriptors
var result : Array<ACMemoEntity> = Array()
var error : NSError? = nil
if let sortedArray = context?.executeFetchRequest(fetchRequest, error: &error){
for object in sortedArray {
if let memo = object as? ACMemoEntity{
result.append(memo)
}
}
}
return result
}
}
データ参照先の変更
自動生成されたCoreDataのソースコードでは、データをアプリのDocumentディレクトリから参照しています。
lazy var applicationDocumentsDirectory: NSURL = {
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return urls[urls.count-1] as! NSURL
}()
これだと、AppExtensionから参照を行うことができないため、AppGroupの共通ディレクトリを参照するように編集します。
lazy var applicationDocumentsDirectory: NSURL = {
let directory = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.com.peromasamune.appextension_coredata")
return directory!
}()
6. AppExtension周りの実装
エクステンション部分の実装を行います。
エクステンション追加時に自動生成されるSLComposeViewControllerクラスのShareViewController
を編集していきます。
MobileCoreServices
とACFramework
をimportします。
import MobileCoreServices
import ACFramework
class ShareViewController: SLComposeServiceViewController {
...
didSelectedPost()
へ投稿ボタンがおされた時の処理を実装します。
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.
let inputItem : NSExtensionItem = self.extensionContext!.inputItems.first as! NSExtensionItem
let itemProvider : NSItemProvider = inputItem.attachments?.first as! NSItemProvider
let contentType = kUTTypeURL as String
if itemProvider.hasItemConformingToTypeIdentifier(contentType) {
itemProvider.loadItemForTypeIdentifier(contentType, options: nil, completionHandler: { (item, error) -> Void in
if let urlItem = item as? NSURL {
println("url : \(urlItem.absoluteString) title : \(self.contentText)")
ACDataManager.createMemo(self.contentText, block: { (completed) -> Void in
let outputItem : NSExtensionItem = inputItem.copy() as! NSExtensionItem
outputItem.attributedContentText = NSAttributedString(string: self.contentText, attributes: nil)
let outputItems = [outputItem]
self.extensionContext?.completeRequestReturningItems(outputItems, completionHandler: { (completed) -> Void in
})
})
}
})
}
}
今回は投稿されたテキストを取得して、CoreDataへ保存するようにしました。
SafariのActionボタンのその他
を押すと、作成したエクステンションが表示されるので、スイッチをONにします。
Postボタンを押すと、入力した内容がアプリ側へ保存されていることが確認できました。
おわりに
今回はAppExtensionとCoreDataを連携させる方法について紹介しました。
AppExtension自体の機能追加はサクッとできてしまいますが、アプリ本体との処理やデータの共有などについては実装前にいろいろと考えておいた方が良さそうです。
AppGroupの概念がなかなかつかめずに結構ハマりました。
アプリのファイル領域とは別の場所を共通領域とするようです。
本記事のサンプルコードは下記githubにあります。
参考資料
AppExtension
- アプリケーション拡張機能 - Apple Developer
- [iOS 8] App Extensionでテンション倍増! – Share Extension編 - Developers.IO
- iOS8のApp Extensionsをつくってみる(Share 実装編)- Qiita