📱Zoi for iPhone
NEW GAME! のコマ検索 - みずぴー日記の成果を用いて、iPhoneからNEW GAME!のコマ検索を行なえるようにした。
経緯
ニコニコでやっているアニメの一挙放送を見ると、PCの前に4〜6時間ほど拘束される。 そのときに、なんとなくXcodeを立ちあげてコードを書きはじめてしまった。
桜Trick、一挙放送みながらiOSアプリつくってたら、だいぶ便利な感じがでてきた。 pic.twitter.com/UIzmPc98DF
— mzp (@mzp) February 21, 2016
機能
コマのインクリメンタル検索
NEW GAME! のコマ検索 - みずぴー日記に各コマのセリフを入力済みなので、それを移植してインクリメンタルに検索できるようにした。
またUIActivityViewControllerを用いたアプリ間連携も行なえる。
Spotlight対応
App Searchに対応したため、Spotlightから検索ができる。
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"))
📷 Live Photo生成、その後
📷 mov/jpegからのLive Photo生成 - みずぴー日記でmov/jpeg から Live Photo生成をできるようにしてから、いくつかの進展があった。
縦MVの発見
デレステにて縦画面MVを実現する方法が発見された。 これにより、iPhoneの画面サイズに適した動画が、簡単に得られるようになった。
GUIの作成
banjunによりGUIが作成された。(参考: movファイルからLive Photoを生成するLoveLiverにGUIをつけた - ツバメになったバリスタ)
https://github.com/mzp/LoveLiver/releases
これによりCLI版で面倒だった
- Live Photoにしたいシーンの選択
- 3秒間の動画の切り出し
- JPEG画像の準備
- Photos.app へのD&D
といった手間がなくなり、Live Photoを量産できるようになった。
Live Photoの量産
上記2つにより、大量のLive Photoが作られるようになった。
毎晩、iCloud Photo Sharing経由で新作が届く。
NEW GAME! のコマ検索
NEW GAME!の全コマをインクリメンタルに検索できるツールを作った。*1
経緯
NEW GAME! 3巻を読んだためNEW GAME熱が上ったので、ゆゆ式を無限に楽しみたかった話 〜 ゆゆ式 Advent Calendar 2014 20日目 〜 - non117's diaryのツール*2を移植し、コマ分割およびアノテーションの付与を行なった。
最初はコマの分割だけのつもりだったが、気がついたら各セリフの入力とキャラのタグづけも行なってしまった。 入力には一週間くらいかかっている。
アノテーションの付与が完了したので、各コマを検索するツールを作成した。
機能
セリフによるインクリメンタル検索
セリフによってコマをインクリメンタルに検索できる。また、該当のコマが単行本のどのあたりに登場しているのかも表示する。
また、すべてのセリフを入力しているため、セリフがないコマの検索もできる。
キャラ指定の検索
特定のキャラを指定した検索もできる。複数指定すれば、同じコマ内に登場しているコマを検索できる。
例えば、「ひふみ」と「ねね」にチェックをいれれば、この二人が同じコマに登場したのは一度しかないことが確認できる。
ページ指定の検索
特定のコマを見つけたあと、同じページにあったコマの検索もできる。 これを利用することで、「プリンを食べたねねっちの表情」といったコマを見つけることができる。
所感
気をぬくと青葉さんの幻覚が見える...
— mzp (@mzp) February 1, 2016
💫Tumblotte 0.1.0
💫Tumblrクライアントを作りはじめた - みずぴー日記 で書いたTumblrクライントをリリースした。
テキスト主体のブログを書きやすくすることを目指している。 そのためMarkdownのライブプレビュー機能などを実装しているが、Reblog機能などは実装していない。
機能
- Tumblrへの投稿・更新
- Markdownのライブプレビュー
- 既存の投稿記事の取得
- 投稿先のブログの切り替え
- 投稿した記事をWebブラウザで開く
前回から、基本的には変わっていない。エラー処理などをだいぶまともにした。
実装していない機能
自分があまり使わないので実装してない。必要になったら実装する。
- MacOS X以外のサポート
- Text以外の記事
ダウンロード
https://github.com/mzp/tumblotte/releases/tag/0.1.0
前回からの変更点
dmgの作成
インストールするときに使うdmgを作った。以下のような /Applications
へのコピーを促すような背景画像も作成した。
コード署名の追加
*.app
を作るときにコード署名をするようにした。
細かい修正
自分で使っていて気になった部分をいくつか修正した。
- 常にライブプレビューを行なうとレスポンスが悪いので、300ms以上キー入力がなかったときにプレビューを更新するようにした。(debounce)
- メインメニューの項目を整理した。
- ライセンスとしてMITライセンスを採用した。
⌨AquaSKK 4.3.4: US配列におけるAZIKの動作改善
US配列 + AZIKにおいて、x[
で 「
の入力を行なえるようにした。
ダウンロード
https://github.com/codefirst/aquaskk/releases/tag/4.3.4
変更内容
US配列 + AZIKでは [
がかなモードの切り替えに使われる。 そのため、[
に割り当てられている 「
の変換が行なえない。
ddskkでは x[
で 元の [
を代用できるようにして、この問題を解決していた。(参考: SKK Manual: AZIK)
AquaSKKでもこの方式を採用することにした。
余談
🎍新年
Copyright(C) 2014-2016 という部分を更新したので、新年を迎えた感じになった。
状態遷移機械
変換の動作は、専用の状態遷移機械記述ライブラリで定義されている。 このライブラリに関するドキュメントは Generic State Machine Library for C++ しか残っておらず、苦労した。
分かったことをメモしておく。
💫Tumblrクライアントを作りはじめた
記事の投稿に特化したTumblrクライアントを作りはじめた。
レポジトリ
https://github.com/mzp/tumblotte
動機
TumblrはリブログするだけのWebサービスでもなく、高機能なブログサービスでもあることに気がついた。
しかし、TumblrのWebUIは長文を書くのには向いていないので、Tumblrのクライアントを作りはじめた。
ElectronやReact.js等のJavaScriptまわりの技術にキャッチアップしたかった、という理由もある。
機能
- Tumblrへの投稿・更新
- Markdownのライブプレビュー
- 既存の投稿記事の取得
- 投稿先のブログの切り替え
- 投稿した記事をWebブラウザで開く
Kobitoを意識しながら作りはじめた。 が、インターネットが不自由な環境で作っていたので、思ったよりUIが似ていない。
名前の由来
どういう名前にするか相談したときに、Charlotteの一挙放送をしていたので、こうなった。
知見
- ElectronでOAuth認証をする方法は Electron. oAuth authentication with GitHubを参考にした。
- Tumblrに投稿にするにはtumblrを使っている。しかし、いくつか機能が不足していたので、自分でforkして拡張した。mzp/node-tumblr
- tumblrwksを使ったほうがよかったかもしれない。
- "electron boilerplate" で検索したら、5つくらいでてきてツラかった。 結局、boilerplateは使わずに自分で書いた。
画像で見る開発の流れ
開発開始直後(2015/12/30)
PureCSSのEmail layoutをざっくりあてた直後。まだ表示ができるだけで、投稿等はできない。
基本機能完成(2015/12/31)
UIの調整(2016/1/1)
FontAwesomeを使って、各ボタンにアイコンを割り当てていった。
ログイン機能(2016/1/2)
ここまでAccessTokenは固定だったので、ログイン機能をつけた。ログイン画面をどうするか迷ったあげく、結局ボタン1個だけを配置した。
投稿先ブログの切り替え(2016/1/3)
投稿先のブログを切り替えれるようにした。
代償
新年二日目にして、通信速度が制限された。
— mzp (@mzp) 2016, 1月 2
⚠️AquaSKK with iCloud
☁️AquaSKKのiCloud対応 - みずぴー日記の内容を整理した上で、パッケージを作成した。 自分で試すためのが主な目的なので、他の人が利用することはあまり想定していない。
Release iCloud対応(1) · codefirst/aquaskk · GitHub
注意点
- 今後、データの持ち方を変える可能性がある。 その際は、同期データがすべて消失する。
- ローカルのユーザ辞書が破壊される可能性がある。実際、開発中に二度ほど破壊した。
- その他、予期しない動作をするかもしれない。
何かあったらiCloud対応PRに書いてほしい。 対応できるかどうかは分からない。
リリースするまでに解決しなければいけない課題
ビルドに必要な証明書の管理
iCloudに接続するために、AquaSKKにひもづいた証明書が必要となった。 その証明書は、ローカルにしか存在していないので、以下のような問題が生じる。
- AquaSKKのビルドを他の人ができない。 そのためチーム開発が困難になる。
- TravisCI上でビルドを行なえない。
FlickSKKとの共有をどうするか
FlickSKKのビルドは@banjunが自分のIDで行なっているので、AquaSKKのiCloud領域にはアクセスできない。
辞書の形式はほぼ同一なので、なんとか共有できるようにした。個人アカウントの制約のようなものなので、起業したほうがいいかもしれない。
x86対応
iCloud対応のために用いているCloudKitがx86_64バイナリしかないため、x86に対応できない。
最近のOS Xもx86では動かないようだし、対応をやめてもいいと思っている。
前回からの変更内容
レコードIDの変更
前回は見出し語は重複しないものとして、見出し語をレコードIDとして用いていた。
しかし、以下のように見出し語が重複している場合もありえるので、見出し語をレコードIDとして用いるのをやめた。
;; okuri-ari entries. わるi /悪/ ;; okuri-nasi entries. わるi /悪/
同期用スレッドの統一
前回は、ローカルの辞書をiCloudに送信するスレッドと、iCloudから取得したデータをローカルの辞書にマージするスレッドを別々にしていた。
そのせいでコードが複雑化していたため、すべて1つのスレッドで行なうようにした。
コードの整理
今後メンテナンスすることを考えて、コードを整理した。
- コピペですませていた部分を共通化した。
- Objectvie-CとC++の部分を増やし、Objective-C++部分を減らした。
SKKのユーザ辞書、完全にぶっこわした感ある
— mzp (@mzp) 2015, 12月 26