2018年3月15日木曜日

使ってみた(App Extension編) その5

「使ってみた(App Extension編) その4」からのつづきです。

前回までで一通りAppExtensionから収容アプリケーションまで、コンテンツとしてString型のデータを受け渡しました。今回はAppExtensionとは直接関係ありませんが、データのシリアライズについて書いておこうと思います。

ここまでのサンプルでは、AppExtensionでコンテンツを保存して、収容アプリケーションの起動時に取得していました。

AppExtensionから複数のデータを受け渡したいこともあります。もちろん、UserDefaultのキーをそれぞれ振って、独立したデータとして扱うのもありですが、意味のあるまとまりのデータであれば、構造体やクラスにしてシリアライズして使いたいところです。


そこで、今回はContentsという構造体を作ってみました(ちなみにクラスでも同様です)。
import UIKit

struct Contents: Codable {
    
    // MARK: - PROPERTY

    var key: Int //何かのキー
    var text: String //内容
    var date: Date //日付

    // MARK: - INITIALIZER
    
    init() {
        self.key = 0 //何かのキー
        self.text = "" //内容
        self.date = Date() //作成日付
    }
    
    init(key: Int, text: String, date: Date) {
        self.key = key //何かキー
        self.text = text //内容
        self.date = date //日付
    }
}
自動でエンコード、デコードできる、Codableというプロトコルがポイントです。

Contents構造体がCodableを備えているのはもちろんですが、中に含まれるプロパティーもCodableである必要があります。

Contents構造体の中にInt型, String型, Date型のプロパティーがありますが、これらの型はすべてデフォルトでCodableなので問題ありません。

このContents.swiftを収容アプリケーションの中に作成します。そしてこのソースにAppExtensionからもアクセスできるように、AppExtensionのTargetのBuild PhasesのCompile Sourcesに指定します。

両方に同じソースを作る必要はありませんからね。



これでContents型が収容アプリケーション、AppExtensionの両方から使用できるようになりました。あとはエンコード・デコードしてUserDefaultに保存・取り出しをします。


AppExtensionでは、Contents型をData型にエンコードしてから、UserDefaultに格納します。
    /// コンテンツをApp Groupのユーザーデフォルトに保存
    ///
    /// - Parameter content:
    private func saveContent(content: String) {
        guard let userDefaults = UserDefaults.init(suiteName: "group.jp.blowbend.ios.test.AwesomeApp") else {
            print("userdefault: app group suite name is nil")
            return
        }
        
        let contents = Contents(key: 123, text: content, date: Date())
        do {
            let data = try JSONEncoder().encode(contents) //Data型に変換
            userDefaults.set(data, forKey: "user default key content") //データを保存
            userDefaults.synchronize()
        } catch {
            print("userdefault: contents encode error!!")
        }
    }


収容アプリケーションでは、UserDefaultからData型で取得してから、Contents型にデコードします。
    /// App Groupのユーザーデフォルトからコンテンツを取得
    private func getContentFromAppGroup() {
        guard let userDefaults = UserDefaults.init(suiteName: "group.jp.blowbend.ios.test.AwesomeApp") else {
            print("userdefault: app group suite name is nil")
            return
        }
        guard let content = userDefaults.object(forKey: "user default key content") ?? nil else {
            print("userdefault: no content")
            return
        }
        guard let data = content as? Data else {
            print("userdefault: no data")
            return
        }
        
        do {
            let content = try JSONDecoder().decode(Contents.self, from: data)
            print("content.key =\(content.key)")
            print("content.text=\(content.text)")
            print("content.date=\(content.date)")
        } catch {
            print("userdefault: JSONDecoder decode error")
        }

        userDefaults.removeObject(forKey: "user default key content") //無条件でデータの削除
    }

これで、構造体をシリアライズしての受け渡しはOKです。

今回はここまで。


「使ってみた(App Extension編) おまけ」に続きます。


♪♪♪


こちらもどうぞ。
使ってみた(App Extension編) その1
使ってみた(App Extension編) その2
使ってみた(App Extension編) その3
使ってみた(App Extension編) その4
使ってみた(App Extension編) おまけ

iOS 11 Programming
  • 著者:堤 修一,吉田 悠一,池田 翔,坂田 晃一,加藤 尋樹,川邉 雄介,岸川克己,所 友太,永野 哲久,加藤 寛人,
  • 発行日:2017年11月16日
  • 対応フォーマット:製本版,PDF
  • PEAKSで購入する

2018年3月1日木曜日

使ってみた(App Extension編) その4

「使ってみた(App Extension編) その3」からのつづきです。

前回はApp Groupの設定ができたところまででした。あとはUserDefaultで保存と読み出しを追加するだけです。


App Extension側でUserDefaultにコンテンツを保存します。
import UIKit
import Social
import MobileCoreServices

class ShareViewController: SLComposeServiceViewController {

    //省略

    override func didSelectPost() {
        guard let inputItems = self.extensionContext?.inputItems as? [NSExtensionItem] else {
            return
        }
        guard let providers = inputItems[0].attachments as? [NSItemProvider] else {
            return
        }

        for provider in providers {
            if provider.hasItemConformingToTypeIdentifier(kUTTypeText as String) {
                provider.loadItem(forTypeIdentifier: kUTTypeText as String, options: nil, completionHandler: { (item, _) in
                    OperationQueue.main.addOperation {
                        if let textItem = item as? String {
                            print("??? AppExtension: textItem= \(textItem)") //printで出力
                            self.saveContent(content: textItem) //コンテンツをApp Groupのユーザーデフォルトに保存
                        }
                    }
                })
                break
            }
        }

        self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
    }

    //省略
    
    /// コンテンツをApp Groupのユーザーデフォルトに保存
    ///
    /// - Parameter content:
    private func saveContent(content: String) {
        guard let userDefaults = UserDefaults.init(suiteName: "group.jp.blowbend.ios.test.AwesomeApp") else {
            print("userdefault: app group suite name is nil")
            return
        }
        
        userDefaults.set(content, forKey: "user default key content")
        userDefaults.synchronize()
    }
}
AppGroupのUserDefaultといっても、UserDefault.init(suiteName:)でAppGroup IDを指定するだけで、ごくごく普通に使用できます。


コンテンツを受け取る側の収容アプリケーションですが、AppDelegateで取得することにします。

もちろん、処理として必要な場所であればどこでもかまわないですが、didFinishLaunchingWithOptionsとapplicationWillEnterForegroundの2カ所で行っておけば、アプリを開いたタイミングで取得できると思います。ここで取得しておけば、アプリ内で自由に取り回しやすいのではないでしょうか。
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        getContentFromAppGroup() //App Groupのユーザーデフォルトからコンテンツを取得
        
        return true
    }

    //省略

    func applicationWillEnterForeground(_ application: UIApplication) {
        getContentFromAppGroup() //App Groupのユーザーデフォルトからコンテンツを取得
    }

    //省略

    // MARK: - METHODS FOR GET DATA FROM AppGroup
    
    /// App Groupのユーザーデフォルトからコンテンツを取得
    private func getContentFromAppGroup() {
        guard let userDefaults = UserDefaults.init(suiteName: "group.jp.blowbend.ios.test.AwesomeApp") else {
            print("userdefault: app group suite name is nil")
            return
        }
        guard let content = userDefaults.object(forKey: "user default key content") ?? nil else {
            print("userdefault: no content")
            return
        }
        guard let textItem = content as? String else {
            print("userdefault: no content")
            return
        }

        userDefaults.removeObject(forKey: "user default key content") //無条件でデータの削除
        print("??? Containing App: textItem= \(textItem)") //printで出力
    }
}

コンテンツの2重取り込みを避けるために、取得できたか失敗したかに関わらず、無条件に削除しています。本来なら、取得したデータの内容をチェックしたりエラーハンドリングをきちんとしておくべきところですが、これはサンプルなので処理のシンプルさを優先して省略しました。

これを実行すると、データが受け渡されていることが確認できると思います。

今回はここまで。


「使ってみた(App Extension編) その5」に続きます。


♪♪♪


こちらもどうぞ。
使ってみた(App Extension編) その1
使ってみた(App Extension編) その2
使ってみた(App Extension編) その3
使ってみた(App Extension編) その5
使ってみた(App Extension編) おまけ

iOS 11 Programming
  • 著者:堤 修一,吉田 悠一,池田 翔,坂田 晃一,加藤 尋樹,川邉 雄介,岸川克己,所 友太,永野 哲久,加藤 寛人,
  • 発行日:2017年11月16日
  • 対応フォーマット:製本版,PDF
  • PEAKSで購入する