みずぴー日記

陽気なプログラマが世界を廻す

📱Zoi for iPhone

NEW GAME! のコマ検索 - みずぴー日記の成果を用いて、iPhoneからNEW GAME!のコマ検索を行なえるようにした。

経緯

ニコニコでやっているアニメの一挙放送を見ると、PCの前に4〜6時間ほど拘束される。 そのときに、なんとなくXcodeを立ちあげてコードを書きはじめてしまった。

機能

コマのインクリメンタル検索

NEW GAME! のコマ検索 - みずぴー日記に各コマのセリフを入力済みなので、それを移植してインクリメンタルに検索できるようにした。

f:id:mzp:20160305085536p:plain

またUIActivityViewControllerを用いたアプリ間連携も行なえる。

f:id:mzp:20160305085447p:plain

Spotlight対応

App Searchに対応したため、Spotlightから検索ができる。

f:id:mzp:20160305085729p:plain

Spotlightのインデックスに登録するタイミングは、悩んだが、

  • 初回起動時
  • 明示的に再インデックスを指示した時

の2つにした。 最初は、初回起動時のときのみにしていたが、インデックス処理でエンバグしたときの対応が面倒だったので、再インデックスボタンを追加した。

インデックスへの追加はApp Search プログラミングガイド: 検索の基本にあるコードをほぼそのまま使っている。

func add(items : [ZoiJson.Item], complete: () -> ()) {
  let si = items.map { self.searchableItemFor($0) }
  CSSearchableIndex.defaultSearchableIndex().indexSearchableItems(si) { error in
    if error != nil {
      print(error?.localizedDescription)
    } else {
      complete()
   }
}

private func searchableItemFor(item : ZoiJson.Item) -> CSSearchableItem {
  let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypePNG as String)
  attributeSet.title = item.script
  if let image = ZoiImage.image(item) {
    attributeSet.thumbnailData = UIImagePNGRepresentation(image)
  }
 return CSSearchableItem(uniqueIdentifier: item.path, domainIdentifier: "zoi", attributeSet: attributeSet)
}

newgame:// 対応

ぐらばくさんが提案していた、newgame:// をカスタムURLスキームとして取り込んだ。(参考: ぐらばく on Twitter: "newgame://3/65/2/2 もいい https://t.co/hC6t3KxYj0")

https://twitter.com/mzp/status/702863419226005505

newgame://巻数/ページ番号/列/行 というフォーマットだが、目次等の存在により書籍のページ番号と先頭からページ数は一致しないので、そこは補正した。 また、ページ内の何コマ目なのかは保持しているが、何列目かは保持していないので、そこの補正も行なっている。

    func toPath(url : NSURL) -> String? {
        if let vol = url.host,
           let paths = url.pathComponents,
           let page = Int(paths[1]),
           let col = Int(paths[2]),
           let row = Int(paths[3])
        {
            let pageStr = NSString(format: "%03d", page + 2)
            let pos = (col - 1) * 3 + row
            return "data/vol\(vol)/vol\(vol)_\(pageStr)_\(pos).jpeg"
        } else {
            return nil
        }
    }

OnDemandResource対応

Testflightにアップロードしようとしたが100MBを越えてしまった。そこで、OnDemandResourceを利用し、必要になったタイミングで画像を取得するようにした。

OnDemandResourceの使い方はオンデマンドリソースでiOSアプリを軽くする - Qiitaを参考にした。

またOnDemandResourceで取得した画像を、ImageViewに非同期で設定するために https://github.com/Haneke/HanekeSwiftを用いてる。標準ではOnDemandResourceからの取得はできなかったので、独自のFetcherを作った。

import Foundation
import Haneke

class OnDemandResource {
    func fetch(fail : NSError? -> Void, succeed : Void -> Void) {
        let request = NSBundleResourceRequest(tags: Set(arrayLiteral: "zoi"))
        request.conditionallyBeginAccessingResourcesWithCompletionHandler() {
          resourceAvailable in
            if resourceAvailable {
                succeed()
            } else {
                request.beginAccessingResourcesWithCompletionHandler() { error in
                    if error == nil {
                        succeed()
                    } else {
                        fail(error)
                    }
                }
            }
        }
    }
}

class OnDemandFetcher<T : DataConvertible> : Fetcher<T> {
    private let resource = OnDemandResource()
    private let getValue : () -> T.Result?

    init(key: String, @autoclosure(escaping) value getValue : () -> T.Result?) {
        self.getValue = getValue
        super.init(key: key)
    }

    override func fetch(failure fail: ((NSError?) -> ()), success succeed: (T.Result) -> ()) {
        resource.fetch(fail) {
            if let result = self.getValue() {
                self.main { succeed(result) }
            } else {
                self.main { fail(nil) }
            }
        }
    }

    override func cancelFetch() {}

    private func main(f : () -> ()) {
        dispatch_async(dispatch_get_main_queue(), f)
    }
}

// 使い方
let fetcher = OnDemandFetcher<UIImage>(key: self.item.path, value: UIImage(named: "zoi.png"))
self.image.hnk_setImageFromFetcher(fetcher,
            placeholder: UIImage(named: "placeholder.jpeg"),
            format: Format<UIImage>(name: "original"))