iOSDC Japan 2017で宇宙について話したよ

iOSDC Japan 2017について

「iOSエンジニアのお祭り」という感じで楽しかったです。
会場やフォトスポット、オープニングなどの演出、ノベルティに至るまで、本当に素晴らしいカンファレンスでした。
(特にTシャツがかっこよくて気に入っています。)
これだけ大規模なカンファレンスの準備は大変だったと思います。スタッフの皆様には感謝感謝です。

日程について

9/15-17、金曜日の夜と土日で開催でした。
実際に行くまでは平日開催のほうが嬉しいなと思っていましたが、いざ行ってみると参加者の人みんなが「休みの日もiOSやりたい」熱気にあふれた人たちだったので楽しかったです。

トークについて

パフォーマンスや、アーキテクチャについての話とかも面白かったですが、特にサービス運用の泥臭い話がとても面白かったです。
来年はもっといろんなサービスの泥臭い話が聞ければいいなと思っています。

自分のLTについて

全体的な感想としては大失敗でした。皆さんの貴重な5分間を無駄にしてすみませんでした。

🍺🙅

そもそもの失敗の原因は、CfP提出のとき。
iOSDCのCfP受付期間中、引っ越しと不動産屋との喧嘩で非常に消耗していて、今年はiOSDCに行かなくてもいいかなという気持ちでした。
しかし、締め切り2時間にビールを飲みながらプロポーザル一覧を見ていたら楽しくなってしまって、勢いでCfPとざっくりとしたシナリオを書いて、「よっしゃなんとかなる」と決断をして送信ボタンを押しました。

酔っ払って書いたCfP

宇宙は可能性に満ちています。
あらゆる状態が同時に存在する多次元宇宙で、我々はどのように情報を設計し、アプリの画面に落とし込めばいいのでしょうか。
このトークでは、アプリの画面の階層を正しく設計し、人類のための画面遷移を人類が開発・運用するためのプラクティスをついてお話しします。

ビールを飲んでるときにCfP書いちゃだめ。絶対。

宇宙

完全に宇宙について無知だったので、採択されてから発表までの資料作成の時間の殆どの時間を宇宙についての勉強に費やすことになりました。
読んだ本はこちら。

勉強すればするほどわからないことが増えていきました。
宇宙ヤバイ

資料作成

相対性理論、標準理論、超ひも理論、M理論、ダークエネルギー、ダークマター…宇宙についての面白い話題はいっぱいあったのですが、どうやってiOSに着地させればいいのか。ずっと悩みました。
もっと設計寄りの話に落とし込みたかったのですが、解決策が見つからず、仕事で作っているアプリのRootViewControllerのコードの解説に落ち着きました。
すべてのアプリに適用できる(無意識に適用されている?)構造なので、共感してもらえるかと思ってたのですが、皆さんの反応を見る限りそうでもないようですね。

発表資料

サンプルコード

GitHub – haranicle/RootViewControllerSample

よかったところ

  • 自己紹介を削ったおかげで内容について話す時間を確保できた
  • いっぱい練習したおかげでほぼ時間ぴったりだった(手元の時計で4分40秒ぐらい)
  • 色確認用スライドを作って事前にプロダクション環境で色とフォントサイズのチェックした
  • なんとか5分間壇上に立っていられた


プロダクション環境でテストする様子

よくなかったところ

いっぱいありますが、CfP以外だと…

  • カンファレンスのLTはナマモノ感があった方が面白いかなと思って、直前にスライドをいじってしまった
    • そもそもアドリブが効かないのにそんなことしちゃだめ
  • LTでコードを出してしまった
    • スピード感がなくなる
    • そんな短時間でコード読めない
  • マイクを持った状態で発表練習をしていなかった
    • マイクを持ちながら話すことになれていないので、マイクを持った瞬間パニック
    • 死ぬほど緊張して、カミカミになってしまった

LTを終えて

LTは大失敗でしたが、懇親会で色んな人に話しかけてもらえたし、面白かったという感想をいただいたので素直に嬉しかったです。
iOSエンジニアみんなやさしい…

iOSDC Japan 2018

期待しています!
次はビールを飲んでいないときにLT以外でCfP出したい。


Collabo Tips #1 にいってきたよ

FiNCさんで開催されたCollabo Tipsという勉強会に行ってきました。

connpass

Collabo Tips(コラボチップス) – connpass

  • デザイナとエンジニアは切っても切り離せない職業
  • 垣根を取り払いたい人のための勉強会

Togetter

@koogawaさんがまとめてくださっています。仕事早い!
2016/8/3 #CollaboTips(コラボチップス) – Togetterまとめ

グラフィックレコーディング

会場ではリアルタイムに発表内容が絵でまとめられていました。すごい!
IMG_7452 2

ストレッチ

勉強会の前にストレッチタイムがありました。体が伸びて気持ちよかったです。

LT

お互いのツールを使ってみよう

Motoki Naritaさん
iOSエンジニア

  • マサカリ禁止!
  • エンジニア/デザイナそれぞれが大切にしていることを知ることが大事
  • ツールが大事なのでは
    • Xcode, Sketch, PhotoShop…
  • エンジニアもデザイナも仕事は、問題を解決すること, 良い解決方法を提供すること
  • お互いのツールを使ってみる
    • 完璧に使える必要はない
  • エンジニア/デザイナどっちがやるべきなのか曖昧な仕事はいっぱいある
    • お互いに歩み寄ると、お互いにできる仕事が多くなる!

デザイナーさん向けSizeClass

yucovin(SAKUMA Nino)さん
デザイナ兼iOSアプリエンジニア

  • 同じようなデザインでも、画面サイズによって制約を変えたいことがある
  • 工数やメンテナンスのしやすさも大事
  • SizeClassを使えばStoryboardを使って画面サイズごとに異なる制約を変えることができる
  • SizeClass使ってみよう!
  • Xcode8でももっと便利になっている

エンジニア主導でMaterial Design化を進めた話

Hiroyuki Setoさん
ノハナ Androidエンジニア

エンジニアでもデザインがちょっと楽しくなるフォントのはなし

cocoponさん
Developer/Designer

  • あるある
    • デザイナがラインハイトの変更をしたいけど、エンジニアがめんどくさがって変更しない
    • もったいない
  • エンジニアがフォント沼にはまった話
  • エンジニアリングもデザイナも同じぐらい深くて面白い
  • お互いの領域のおもしろさを知って

コードが書けるようになる

yoneappさん
フリーランス iOS & Rails

  • コードが書けるとは
    • 自分が作りたいものが作れること
    • 報酬がもらえるようになること
  • Webは難度低〜高の技術が必要
    • まずは、HTML+CSSからはじめてみよう
  • アプリは難度中〜高の技術が必要
  • バックエンドにチャレンジにするには、サービスをリリースしてみるといい
  • エンジニアとデザイナで教え合うことが大事

GitHubすら知らなかったデザイナーがデプロイするまでなったわけ

Ryo Usamiさん
Wantedly デザイナ

  • Wantedlyでもデザイナもコードを書く
    • Rails
    • iOS
    • Android
  • 何のためにデザイナがデプロイするのか
    • 仕組みとして良いUIを保てるようにするため
  • Development Flow
    • デプロイする前にデザイナのチェックがる
    • UILGだとデプロイ
    • デザインの保守のためにコードの提案までしていく
  • どうやったらコードを読めるようになるのか
    • issueはすべて、diffも9割みている
  • すべては良いプロダクトにするため

Creativeな時間を確保せよ!

Kenさん
Androidエンジニア

  • FiNC
  • Creativeな時間を確保する
  • Creativeとは”こだわり”
  • Less is more
  • iPhone4sは名機
    • デザインを分解すると少ないパーツでできている
  • アプリで考えると
    • いろんなコンポーネントを作りまくらずに、できるだけ共通化する
  • God is in detail
    • 細かいところに時間をかけられる様にしておく
  • 共通化によりこだわるべきところが見えてくる
  • 共通化はエンジニアとデザイナが協力しないといけない

自分でできることを増やしてみるというのは結構楽しかったという新米iOS開発者の小話

自分でできることを増やしてみるというのは結構楽しかったという新米iOS開発者の小話 from Reiko Gotou

ReikoGotouさん

  • 先方がデザインして、開発だけ担当している
  • デザインがイケてなくて開発のモチベが上がらないことがある
  • デザイナには…
    • HIGを読んでもらう
    • 全部だと200ページ以上あって大変なので、読んで欲しい場所を指定した
    • iOS標準のアプリを使ってもらう
    • そこに正解のデザインがある
  • 企画xデザインの段階でHIGを理解してもらっていると開発も楽
  • デザイン部に頼むより自分でやったほうが速いことがある
    • 素材の書き出しなど、デザインの変更がないものは自分でやったほうが速い
    • Sketchを使ってみた
  • Sketch
    • iOSアプリ設計のために作られたようなツール
    • 学習時間は2時間ですむくらい簡単

共創をドライブさせる

共創をドライブさせる、ユーザーを向いたモノづくり

yoshiki_kojima_7さん

  • technical-creator.comというブログを書いている
  • モバイルアプリの開発現場は徐々に肥大化してきている
  • コラボレーションツールが大事
    • コミュニケーション
    • ドキュメント共有
    • プロジェクト・タスク管理
    • プロトタイピング
  • ツールの導入では根本的な問題は解決しない
  • チームを支えるのはユーザ
  • ユーザには簡単に会える
  • いいものを作りたい
    • 広告主、上司、株主 < ユーザ
  • アベンジャーズを目指そう!
  • iOSエンジニア募集中

1年間で起きたデザイナーとエンジニアの変化

h_matsuhisaさん

  • みんなのウエディング
  • みんなの大切な日を増やす
  • 買収を契機にいろいろ変わった
    • サービスのParlからRailsに移行
    • ユーザ価値の設計をGitHubのissue上で議論している
    • プロトタイプの導入
    • デザインガイドライン
    • アジャイル
    • 1週間スプリント
    • できるだけ小さいチームで開発する
  • 起きた変化を考える
    • エンジニアがデザイナの仕事をするようになった
    • デザイナはより深いことができるようになった

デザイナーだけどコーディングできないとダメ?

mazco/matsu_saoさん
デザイナ

  • はてな
  • コーディングをはじめたのは1年前
  • デザイナーもコーディングできないといけないのか?
    • どっちでも良いと思う
    • プロに任せたほうが良いのでは
  • 問題点の整理が大事
  • 会社と個人の興味が一致しているので、いまはコーディングしている
  • できることが増えると話題がひろがって楽しい!

エンジニアからデザイナーさんへお願いしたいこと

tomomasa_masuzawaさん
エンジニア

  • フォントは大きめに、できればシステム設定にも対応してほしい
    • 画面にいっぱい情報を出すために小さくするのはやめて
    • IBのデフォルト値は17pt
  • 独自フォントはやめたほうがいい
    • Dynamic Typeが動かない
  • ボタンには文字を入れましょう
    • iOSでは文字が入っている
    • Androidでは文字が入っていない
    • プラットフォームごとの違いも考慮しよう
  • iOSとAndroidの操作系は別物なのでUIを別にしよう
  • 見えない機能はつかわれない
      - Long Pressは気づかれない

新米デザイナーが学んだこと

おかぴーさん
デザイナ

  • 高校の時は仕訳帳ばかり書いていた
  • デザイナーになって苦労してきたこと
    • 指示書がぐちゃぐちゃ
    • 素材の命名がぐちゃぐちゃ
    • 素材のサイズが間違っている
  • 開発スピードが遅くなる
  • ガイドライン
    • Zeplinを使ってデザイン指示
  • コミュニケーション
    • 密に 席が近い
    • 漏れなく デザインレビュー会
    • 丁寧に 指示書
  • エンジニアもデザイナに寄り添ってほしい

Sketchとその仲間たち

Daiki Matsudateさん
iOSエンジニア

  • Zeplin
    • エンジニアがほしい情報が全てある
  • Simpli
    • エンジニアがUIを作るには時間がかかる
    • 自動でUIパーツを作ってくれる
    • StoryboardにD&Dで配置するだけでいい
    • 画像を自動でAssets Catalogに入れてくれるので便利

エンジニアには本当にデザインのセンスが無いのか?

akio0911さん
iOSエンジニア

  • デザインの原則
  • ソースコードに似ている!
    • 反復 = シンタックス
    • 整列 = インデント
  • エンジニアはきれいなコードを書きたいと思っている
    • この感覚をデザインに持ち込めばいい
  • アプリのアイコンを作ってみた
    • 作ればわかる

懇親会

豪華なケータリングと🍺をいただきました。ごちそうさまでした!
IMG_7453

cocoponさんの発表で紹介されていた本を見せていただきました(もう売ってないらしい…)
IMG_7457

FiNCさんのオフィスを見学させていただきました。
おしゃれオフィスで、エンジニアには高級な机と椅子が提供されるらしい。(羨ましい..!)

オフィス内に簡単なジムのようなものがあって、仕事終わりに運動できるらしいです。(羨ましい..!!!)
IMG_7460


potatotips#26に行ってきたよ

ブログまとめ枠で参加したのでまとめます。

connpass

potatotips #26 (iOS/Android開発Tips共有会) – connpass

会場はSupershipさん

supership

青山にあるオシャレなオフィスでした。
会場ではお寿司や、オードブル、ビールが用意されていました!
おいしかったです!

発表

Can we live in pure Swift world?

Can we live in a pure Swift world? from toyship

@TachibanaKaoruさん
株式会社VOYAGE GROUP所属

  • KotlinでAndroidアプリ作ってる!
  • iOS/Androidエンジニア募集中!
  • Swift書きたい
    • 既存アプリだとobjcとSwift共存してたりする
      • objcとSwiftを共存させていると困ることがある
        • Swiftの一部機能はobjcからは使えないなど
  • pure Swiftでも困ることもある
    • selector指定ができない
      • コンパイルは通るが unrecognized selector になる
    • objcから呼べないものはselector指定ができない
      • objcから呼べるかどうかは、objcのmember tableに入っているかどうかで決まる
      • 中間ファイルを見るとobjcから呼べるかどうかわかりやすい
        • objcで #import "MyProduct-Swift.h" と書いて、コマンド+クリックするとXcodeで見れる

感想

  • objcから見えるSwiftのメソッドでラップしてあげれば、selector指定できるようになりそうだと思いました
  • Swiftで書くとobjcにはなかった問題が発生してしまうのは、あるあるネタですね

5分でわかるTextKit

5分でわかるText Kit from Ryota Hayashi

@hayashi311さん
bitFlyer所属

  • (図が多くてうまくまとめられませんでした)
  • iOSエンジニア募集中
  • TextKitで文字をレンダリングしている
  • グリフ: レンダリングするときの一番小さい単位
  • 文字とグリフは 1:1 ではない
  • 文字列 != グリフ
  • レイアウト = グリフの座標
  • NSLayoutManagerがレイアウトの描画や、タッチイベント(どの文字が選択されたか)なども管理している
  • 自分で実装しようとすると大変なのでTextKitはえらい

感想

  • グリフとレイアウト、全く意識していませんでしたが、非常に興味深い内容でした
  • こういう理解しにくい概念を図つきで解説してくれる発表はありがたいです

PUSH通知の許可をよりもらうためのUI考察など

PUSH通知の許可をよりもらうためのUI考察など from Tsuyoshi Yonemoto

@yoneappさん
iOS & Rails フリーランス

  • Techinsightをリリースした!
  • 今からParseは無い
  • OneSignalを使うことにした
    • 完全無料
  • 不安点
    • Push通知が遅れることがある
    • ビジネスモデルが不明だからいつまで継続するかわからない
  • Push通知の許可について
    • UIパターンを調べてみました
         – 唐突に表示系
         – 事前説明系
         – 事前説明 + 擬似アラート系
         – 通知許可許可系
    • どのパターンが一番効果が高いのかわからない
    • 唐突系以外は、Push設定をリセットされたときのことも考えないといけない

感想

  • 実際に試して、結果がどうなるかが気になる内容でした
  • 意外とユーザはPush通知の許可ダイアログについて理解しているのかもしれないなと思いました

NSURLProtectionSpace Authentication Methods

iOS の通信における認証の種類とその取り扱い from niwatako

@niwatakoさん
はてな所属

  • はてなからは2人のエンジニアがtry!Swiftで登壇!
  • ちゃんと認証のdelegateを扱ってないとWKWebViewが真っ白になることがある
  • 認証の種類を確認(チャレンジするという)
  • 認証の種類がいっぱいある
  • Webブラウザを作る場合以外は、全部サポートしなくてもいい
    • HTMLFormは実装する必要がない事が多い
    • iOS8,9で挙動が異なることがあるので注意
  • 結局Appleのサンプルコードを見るのがわかりやすい
  • 続きは次回yidevで!

感想

  • UIWebViewではなかった、WKWebViewのつらみのお話でした
  • 5分じゃ理解しきれなかったので、yidevでの発表が楽しみです

皆に相談したい「私の現場での試行錯誤後のiOS開発方針」

私の現場での試行錯誤後のiOS開発方針 – Qiita

@satoshi0212さん

  • 開発方針についてシェアしながら相談したい
  • 未だにMRC
  • オープンソース試用は控えめで直接プロジェクトに追加
  • 基本的にviewDidLoad内で画面レイアウト生成
  • 画面表示変更用メソッドrefreshを用意し、それを使用 <- protocol
  • はてなでは2日間でデザイナにStoryboardの使い方を教えて、デザイナがStoryboardをいじっているチームもある

感想

  • 個人的に、viewをカスタムするコードは量が多くなるし、テストできないコードだと思うのでできるだけstroyboardに書きたいかなと思いました
  • storyboardコンフリクトはそんなに怖くないので、バンバンstoryboard活用していけばいいと思います

脱swift初心者するための2つのきっかけ

脱swift初心者するための2つのきっかけ from Daiki Mogmet Ito

@mogmetさん

  • 関数型言語を使ってみよう
  • {}を減らそう
    • guardを使って早期リターンするとインデントを減らせる
    • ifのかわりにwhereを使って、インデントを減らせる

感想

  • @mogmetさんのバッチリスーツかっこよかったです
  • whereでインデントを減らすというのは、いいアイデアなので使っていこうと思います

HTTPリクエストをフックしてハック

@yukiasaiさん
マネーフォワード所属

  • iOS,Android,Rubyのエンジニア募集中!
  • WebAPIできてないけどアプリ側開発しないといけない
  • WebAPIをモックできるKageeを作った
    • https://github.com/yukiasai/Kagee
  • NSURLProtocolを使えば、NSURLSessionの通信をモックできる

感想

  • Kagee便利そうだとおもいました
  • ライブラリの使い方だけでなく、仕組みについても理解できたのでよかったです

古き良きsendAction

@tokoromさん

  • UIApplicationのsendAction
    • iPhone OS 2のころからあるメソッド
    • app.sendAction(“hoge”, to:xx, from: sender, forEvent: nil)
      • 使いドコロある?
      • RxSwift使ったほうがいいんじゃない?
    • Responder Chanin in UIKit
      • 奥まった階層でも発火
      • 階層のどこでも受けられる
      • 階層が複雑化する昨今では使いやすいかもしれない
    • インタフェースが古い
      • 引数にObjectを渡しづらい
      • 新しいインタフェースでラッピングすれば使える

感想

  • みんなちがってみんないい
    • UIApplicationのsendAction
    • NSNotificationCenter
    • Delegate
  • どれを採用するかは、好みの問題が大きいと思いました

宣伝

全体の感想

  • iOSはSwift, AndroidはKotlinの話題が多い印象を受けました
  • 一方で、トレンドに流されないコアな話題もあり、勉強になりました
  • 話しやすい雰囲気で、非常に楽しい勉強会でした
  • 運営のみなさま、お疲れ様でした!ありがとうございました!

第20回 potatotiosにいってきたよ

potatotips

毎回人気で発表枠でもなかなか参加できない勉強会potatotipsにいってきました。
(発表者枠で申し込んでたけど、抽選に外れて参加できそうになかったのでブログまとめ枠で滑り込みました)

詳細はこちら
【第20回】potatotips(iOS/Android開発Tips共有会) – connpass

会場はVOYAGE GROUPさん。
会場には、ポテトチップス、ピザ、ビールだけでなく寿司まで用意されていて豪勢でした。

発表の数が多くて、タイピングが追いつかなかったのでiOSの発表だけまとめてます。


CoreSpotlight Optimization

@sakmtechさん
VOYAGE GROUPの人

CoreSpotlightとは

  • iOS9新機能の1つ
  • アプリ内の検索結果をSpotlightで表示することができる
  • タイトル、説明、キーワードを設定できる(キーワードは非表示)

どうやったら上に表示されるか

  • タイトル、説明文、キーワードの順で優先してマッチしたものが上に表示される
  • ひらがなで検索した時もカタカナや漢字も検索でマッチする
    (例)なつ->夏,懐 etc..
  • トップヒットに乗るのは1アプリ3つまで
  • 先にインストールしたアプリが上に表示される
  • まだまだbetaなので結果は変わる可能性あり…

watchOS1のアプリをwatchOS2移行する

watchOS1 to watchOS2 from Motoki Narita

@mo_to_44さん
ネクストの人

  • watchOS 2 Transition Guideが提供されている(ここに重要な事がさらっと書かれているらしい)
  • watchOS1から2への最大の変更点はWatchのExtensionが動作する場所ががiPhoneからWatchに変更になっていること
  • データストアはiPhoneとwatchの両方にあるようになった
  • iPhoneとwatchのデータを同期するWatch Connectivityを使用する

とにかく明るいCore Spotlight

とにかく明るいCore Spotlight from 今城 善矩

@yimajoさん
エンジニアアウトプットランキング – Stargazerの人

TweetRainというアプリでニコニコ動画みたいに#potatotipsハッシュタグが付いているTweetがスライドにオーバーレイ表示されていました。

ユーザ目線での使いどころ

  • 電子書籍リーダのタイトル
    • KindleとかiBooks両方に書籍を入れているので、CoreSpotlightで両方を検索できればうれしい
  • サンプル(iOSDayByDayというものがある)
  • キーワードだけじゃなくてEメールなど検索できるものが160個ぐらいある

参考資料(見たほうが良い資料)


英語版リリース前に知っておきたいこと

@horimislimeさん
トレタの人

NSLocalizableString

  • Localizableの使い方をミスると大変
  • こんな風にすると姓名が逆になっちゃうことがある
[NSString stringWithFormat: @&quot;%@ %@&quot;, NSLocalizedString(@&quot;lastName&quot;, @&quot;&quot;),  NSLocalizedString(@&quot;firstName&quot;, @&quot;&quot;)]

NSPersonNameComponents&Formatter

  • アプリを実行しているロケールに応じて、いい感じに姓名の順番を整えてくれる
  • suffixで”Mr”,”Mrs”や”様”などをいい感じにつけられる
  • iOS9からしか使えない

stringsdict

  • 複数の時に〜sをつけたりしないといけない
  • stringsdictを使えばXMLを書いておくだけで、〜sを付けられる

NSDataIntervalFormatter

  • 時刻の区間をいい感じのフォーマットの文字列にできる

画像リソース

  • ButtonにBackground Imageを使っているとボタンのサイズが変わった時に大変
  • 使わない方がいい(文字のタイトルはLabelで!)

Crowdin

  • 翻訳支援サービスCrowdinが便利
  • XLIFFを書き出してアップロードするのみ
  • 外部翻訳者とのコミュニケーションとか進捗管理が楽
  • APIもあるのでCIと連携することもできる

PUSH通知証明書作成ツールを作った

PUSH通知証明書作成ツールを作った from Tomoki Hasegawa

@tomzohさん
デジタルサーカスの人

iOSのPUSH通知のための証明書作成

  • 3ヶ月に1回更新しないといけない
  • Macじゃないとだめ
  • 黒い画面での操作

ツールを作った

  • WebからPush通知の証明書をDLできる
  • Dev/Prod対応
  • テスト送信もできる
  • p12ファイル対応
  • CakePHP製

今日から使える!

monotty


CommandLineTool

Command Line Tool in swift from Yusuke Kita

@kitasukeさん
メルカリの人

SwiftでCommand Line Tool

  • Xcodeで作ることができる
  • Cathegeのコードを見ると作れる(最初の方のコミットがかなり細かいので、それを追えばわかりやすい)

  • プロジェクト設定のコマンドラインツールのテンプレからは作れない
  • Embedded Content Contains Swift Code for framework targetがデフォルトでNOになっているけどYESにしないとSwiftコードが実行できない

2時間の暗号化済み動画データはどうやって再生するのか

@satoshi_0212さん

問題

  • 2時間の番組はメモリ上に乗るのか
  • 暗号化&復号化が必要
  • オフライン再生

方針

  • 標準のAVPlayerのクラス+HLSを使用する方法
  • ffmpegのコードを使用する方法
  • 納期が短かったため前者を採用

実装

  • 動画ファイルを暗号化キーで暗号化して分割してtsファイルを複数作成
  • tsファイルの再生はAVPlayerで再生できないのでcocoaHttpServerでiPhone内にHTTPサーバを立ててストリーミング再生
  • 再生はAVPlayerにHLSの再生リストを渡すだけでOK
  • 無事にAppStoreに公開できた

SFSafariViewControllerとOAuth

https://speakerdeck.com/kshuin/oauth-with-sfsafariviewcontroller

@huinさん
フリルの人

SFSafariViewController

  • Safariと同じだから、使い慣れたインタフェース
  • SafariのCookies情報を利用可能
  • PW/クレジットカードのオートフィル
  • ATSに引っかからない

OAuth認証

  • アプリ内のWebView認証はユーザからみて安全でない
    • JSつかってid/pwを抜いたりできてしまう
  • SafariViewControllerをつかってOAuthしましょう(Appleもこれを推奨しているらしい)

感想

WWDCからしばらくたって、みなさんが収集したiOS9の知見が聞けて非常にためになりました。
ブログまとめ枠で参加すると、枠が空いているし、強制的に集中して聞くようになるので、結構良かったかなと思いました。


ベストアプリ勉強会にいってきたよ

今日、SmartNewsさんで開催されたベストアプリ勉強会にいってきたのでメモ。

ベストアプリ勉強会

ベストアプリ勉強会 – connpass
#startups_best_apps

登壇者

株式会社VASILY 村田さん

iQON(アイコン) – コーディネート・レディースファッションアプリ

メンバ構成

iOS x2 ObjC
Android x2 Java
Web x6

DL数はiOSがAndの倍

株式会社Wantedly 杉上さん

Wantedly(ウォンテッドリー)「はたらく」を面白くする

メンバ構成

iOS x4
Android x1
Web x4
Designer x1

株式会社SmartNews 町野さん

SmartNews

前身はCrowsnest。

2つの決断

CrowsnestからSmartNewsに変更するにあたって、2つの決断をした

  • アプリオンリー化
  • パーソナライズをなしに

メンバ構成

社員全体の半分以上は50%以上にする。
全体 x40
エンジニア x20
iOS x2
Android x2
来月引っ越し予定(渋谷 明治神宮寄り)

パネルディスカッション

なぜベストアプリになれたか?

SmartNews

ユーザからの評価はGoogleもAppleも見てるはず。
継続的に使われているかも見ているはず。かっこよさ。

VASILY

2012年当時にないアプリをつくることが出来た。
良くも悪くもプラットフォームのルールに沿ったアプリを作った。
Android特有のUIを採用した。

Wantedly

今までにない価値を提供できた。
UI/UXにこだわった。わくわくしながら使えるようにした。
最新技術をできるだけ早く取り入れた。iOS8の新機能やiPhone6,6plus対応も早めに出せた。

KPIはどうしているか?

Wantedly

ユーザが会社に遊びに行ってかどうかを測定している。
1年間で2倍ぐらいになっている。

VASILY

DAU。女の子が雑誌を持ち歩かなくてもファッションメディアに触れられることが大事なので。

SmartNews

アクションの数/DAU。滞在時間に近い値。競合はゲーム会社。

競合?

SmartNews

ニュースアプリだけが競合ではない。
時間の取り合い。

Wantedly

転職大手が競合だが、違った価値を提供している。

VASILY

zozoのWEARとか、女性が好きそうなアプリ。

グローバル展開は?

Wantedly

linked.inとかfacebookとやりあっていきたい
アジアに向けて展開していきたい。

SmartNews

今、一番力を入れている所。
アメリカに拠点を作っている。
現地のパブリッシャーとやりとりしている。
現地でも採用活動している。

VASILY

海外展開も確実にしていきたい。
1年、2年後ぐらい目標。

海外対応の難しいところは?

VASILY

海外のECサイトのつなぎ込み。
準備は進めているが苦戦している。

Wantedly

ローカライズが大事。
転職に関する考え方が国によって違う。
Androidから参入予定。
サーバは日本に

SmartNews

英語圏からの展開。

開発手法について

VASILY

Androidファースト。
ボタンの色を思い切って変えてみるとか。
機能全体をガッツリ見なおしている。
ストア公開に待ち時間がないのがいい。いっぱい試作できる。

Wantedly

iOSファースト。
海外はAndroidファースト。

SmartNews

特に何ファーストかは決めていない。
実験的なことは、Androidでやってる。

リリースマネジメント

SmartNews

リリース日は決めているけど、柔軟に。
Android、iOSともにTravisで自動ビルドしている。
リリース前にQA期間をとっている。

VASILY

Androidは毎週リリース。
週のはじめにタスク決定。
両方自動ビルド。常に開発者の端末には最新のビルドがある状態に。

Wantedly

Pjを発足させる。2週間-2,3ヶ月ぐらい。
Pjごとに人をアサイン。
2週間Pjなら2,3日、2,3ヶ月なら1週間ぐらいQA期間。
テストコードはかけていない。
人手でテストしている。

デザイナとの協業はどうしているか?

SmartNews

デザイナ専業の人はいない。
iOSのエンジニアが元デザイナ。
アドバイザがいる。

Wantedly

エンジニア、デザイナが同一人物。

VASILY

フルタイム2人+フリーランス1人+fladdictさん(週1)
イメージで発散させて、デザイナ、エンジニアが協力して実現していく。

マネタイズは?

Wantedly

募集をかける企業からお金をもらっている。
ユーザからはお金をとっていない。

SmartNews

広告用のサービスがある。
Qiitaチーム、Slackとかを全員同じアカウントで使っている。

VASILY

提携先ECサイトからお金をもらっている。数億/月。
広告事業。

プラットフォーマとのミーティング

SmartNews

ベストアプリ取るにあたってプラットフォーマとの付き合いも大事。

Wantedly

Swiftでアプリを出すときにサポートがあった。

VASILY

稀にミーティングが有る。

プロモーションはどうしているのか

VASILY

TVCMをやっていた。ユーザからの誘い込みをやりたい。

Wantedly

応援。応援とAppStoreからの流入が半々ぐらい。

SmartNews

とってきたユーザをいかに逃さないかに注力している。

ユーザの行動追跡はどうしているか

SmartNews

横スワイプの数。PV。
Google Analyticsと自前ので計測。

VASILY

何番目のカードがLIKEされたかなど、細かい行動を取得している。

Wantedly

Google Analyticsを使っている。
栞では、記事を読むのにどれくらい時間をつかっているか計測。
アプリによって異なる。

ベストアプリをとるためのアドバイス

SmartNews

ユーザに対して向き合うこと。

VASILY

プラットフォームのいうことをきくだけでなく、ユーザのためにツールとして使用する。

Wantedly

作っていてわくわくするものが作れていること。

感想

いつもつかってるアプリの開発者の人の話がいっぱい聞けて良かったです。
もっと技術よりな話もききたかったなぁ。


WatchKitでWKInterfaceImageを使ってアニメーション

この投稿はWatchKit Advent Calendar 2014の13日目の記事です。

WKInterfaceImageとは

WatchKitで画像を表示するためのクラスです。
単に画像を表示するだけでなく、アニメーションさせるための仕組みもあります。

静的な画像を使用したアニメーション

静的な画像を用いてパラパラ漫画のようにアニメーションさせる場合は、Watch AppのStoryboardとアセットカタログだけで実装できます。

動作イメージ

moonanime
※シミュレータではもうちょっとぬるぬる動きます。

アセットカタログ

アニメーションさせたい画像を下図のように、<画像名><連番(0はじまり)>のImage Setを作成します。

xcassets

※Xode6.1のスクリーンショットです。

Storyboard

WKInterfaceImageをInterface Controllerに配置し、プロパティに以下のように設定します。

Image:<画像名>
※連番は含めないので注意
Animate:Yes
Duration:1コマを表示する時間(秒)
Animate on Load:チェック

これだけでパラパラ漫画のようにアニメーションできます。

再生制御

WKInterfaceImageの以下のメソッドを使って、再生の制御をすることもできます。

動的な画像を使用したアニメーション

WatchKit Extension側でUIImageを動的に生成して、WKInterfaceImageでApple Watchに表示することもできます。

今回は1秒ごとにランダムな色で塗りつぶされたUIImageを生成し、Apple Watchで表示してみました。

動作イメージ

dynamicanime

UIImage作成処理

func generateImage() -> UIImage {
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), false, 0)
    let context = UIGraphicsGetCurrentContext()
    
    func colorValue() -> CGFloat{
        return CGFloat(arc4random_uniform(255)) / 255.0
    }
    
    CGContextSetRGBFillColor(context, colorValue(), colorValue(), colorValue(), 1)
    
    CGContextFillRect(context, CGRectMake(0, 0, 100, 100))
    
    let generatedImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    
    return generatedImage
}

タイマーの設定

var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "updateImage", userInfo: nil, repeats: true)

func update() {
    // dynamicImageはWatch AppにあるWKInterfaceImageをOutletで参照したもの。
    dynamicImage.setImage(generateImage())
}

これだけ!

まとめ

現状のWatchKitでは、ダイナミックに画面を更新するには、WKInterfaceImageを使う必要がありそうです。
動的にUIImageを作る方法をしっかりとマスターしておいたほうが良いですね。

参考

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


iOSでAppExtensionを使わずにカスタムキーボード

この投稿はiOS Advent Calendar 2014の13日目の記事です。

こんにちは。13日目担当のharanicleです。
先にあやまっておきますが、小ネタです。。

iOS8のカスタムキーボード

iOS8になってAppExtensionという仕組みで、カスタムキーボードを作成できるようになりました。
僕も1つカスタムキーボードアプリをリリースしています。
良かったら買ってね!

特殊文字キーボード

カスタムキーボードは便利だけど、自分のアプリだけで使えるカスタムキーボードがほしい!
カスタムキーボードもいいけど、標準キーボードが一番使いやすい!
そうだ、標準キーボードを拡張すればいいんだ!という奇特な人のためにこんなものを作ってみました。

customkeyboard

解説

UITextEffectsWindow

UITextEffectsWindow

iOSアプリのキーボードは、UITextEffectsWindowという、
AppDelegateが持っているUIWindow(簡単のためメインウインドウと
呼びます)とは、別のUIWindowで表示されます。
なので、キーボードの上に何かを表示するためには、
メインウインドウとは別のUIWindow(簡単のためにサブウインドウと呼びます)
を作成し、UITextEffectsWindowの上にサブウインドウを表示する必要があります。

サブウインドウの開閉

UIWindowを開く

UIWindowを開く処理のコードです。
sushiWindowを作成し、windowLevelをUIWindowLevelNormalより少し大きい値をセットして、UITextEffectsWindowよりも上にsushiWindowを表示しています。
objc_setAssociatedObjectでsushiWindowのオーナーを設定しています。
sushiWindowは閉じるときにnilを代入して開放したいのでOptional型にしています。
また、windowにお寿司の絵文字を入力できるボタンを設置しています。
(ゴリゴリのハードコーディングですみません…)

func openSushiWindow() {
    sushiWindow = UIWindow(frame: CGRect(x: 230, y: 621, width: 42, height:42))
    sushiWindow?.windowLevel = UIWindowLevelNormal + 5
    sushiWindow?.makeKeyAndVisible()
    sushiWindow?.addSubview(sushiButton as UIView)
    sushiWindow?.backgroundColor = UIColor.orangeColor()
    
    objc_setAssociatedObject(UIApplication.sharedApplication(), &sushiWindowKey, sushiWindow, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
}

UIWindowを閉じる

objc_setAssociatedObjectでnilをセットして、サブウインドウの所有権を開放しています。
sushiWindowにnilを代入してオブジェクトを開放しています。

func closeShushiWindow() {
    // サブウインドウを破棄
    objc_setAssociatedObject(UIApplication.sharedApplication(), &sushiWindowKey, nil, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
    sushiWindow = nil
    
    // メインウインドウをキーウインドウにする。
    UIApplication.sharedApplication().windows.first?.makeKeyAndVisible()
}

キーボード表示、非表示のイベント

NSNotificationCenterで取得できます。

Observerの設定

キーボード表示、非表示のイベントを受け取るObserverの設定と除去です。
今回は、viewDidAppearとviewDidDisappearで実行しました。
キーボード表示時にサブウインドウを開き、非表示時にサブウインドウを閉じるようにしました。

override func viewDidAppear(animated: Bool) {
    // キーボード表示、非表示の通知を受け取る。
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidShow:", name: UIKeyboardDidShowNotification, object: nil)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidHide:", name: UIKeyboardDidHideNotification, object: nil)
}

override func viewDidDisappear(animated: Bool) {
    // 通知を受け取らないようにする。
    NSNotificationCenter.defaultCenter().removeObserver(self)
}

/**
キーボードが表示された時の処理
:param: notificaiton 通知
*/
func keyboardDidShow(notificaiton:NSNotification) {
    println(__FUNCTION__)
    openSushiWindow()
}

/**
キーボードが非表示になった時の処理
:param: notificaiton 通知
*/
func keyboardDidHide(notificaiton:NSNotification) {
    println(__FUNCTION__)
    closeShushiWindow()
}

サンプルコード

以下に今回のコードを置いておきます。
haranicle/CustomKeyboardWithoutExtension

まとめ

この方法だとAppExtensionを使っていないので、iOS7以下でも使用できますね。
きっと、LINEのスタンプを選択するUIはこの方法でやってるんだと思います。

参考


Apple WatchのNotification画面のカスタマイズについて

この投稿はWatchKit Advent Calendar 2014の10日目の記事です。

Custom Notification Interfaces

Apple Watchのnotificationの画面は2種類あります。
詳しくは以下参照。
Apple WatchでのNotification受け取りについて | blog.haranicle.net

Watch Appのターゲットを作るときにNotificationにチェックを入れておくと、Storyboardに以下のようなものが出来上がります。

notification_static_dynamic

Dynamicの方はOptionalなので削除することもできます。
また、削除したあとに、以下の手順で手動で作ることもできます。

  • Interface ControllerをStoryboardを配置し、Staticから配置した Interface ControllerにSegueを伸ばし、”dynamic controller interface”をクリック
  • または、後述のカテゴリ編集画面のチェックボックスで追加することもできます

NotificationはGlanceと異なり、1つのWatch Appで複数持つことができます。

Notificationが届くと、WatchKitはDynamicのInterfaceを表示しようとします。
あなたがDynamicのInterfaceを提供しなかった場合、または何らかの理由でDynamicのInterfaceが利用できない場合は、Staticのinterfaceを表示します。
また、あなたがStaticのInterfaceを表示するように明示することもできます。
DynamicのInterfaceを構成するためには以下のメソッドを呼び出す必要があります。

  • didReceiveRemoteNotification:withCompletion:
  • didReceiveLocalNotification:withCompletion:

これらのメソッドが”WKUserNotificationInterfaceTypeDefault”を返すと、明示的にStaticのInterfaceを表示することができます。

Configuring the Notification Type of a Custom Interface

各NotificationのInterfaceは、Apple Watchにいつ使用するかを教えるために、割り当てられたNotificationタイプを持っている必要があります。
Notificationはカテゴリの値をPayloadに含むことができます。
もし、Notificationがカテゴリの値を含んでいない場合は、デフォルトのNotificationの画面を表示します。

届いたNotificationとInterfaceを対応付けるために、それぞれに同じカテゴリの値を設定する必要があります。
Interfaceのカテゴリの値はStoryboardで設定できます。
Nameの値は一意である必要があります。

notification_type_config

リモートNotificationを生成するときに、Payload内にカテゴリの値を含めます。
カテゴリの値はStoryboardで指定したものと同じである必要があります。

Configuring the Static Notification Interface

StaticなNotificationのInterfaceを作る上でのルールは以下です。

  • すべての画像はWatch Appのバンドルに含めなければいけません。
  • Interfaceに操作できる何か、テーブル、地図を含めてはいけません。
  • InterfaceのnotificationAlertLabel outletはLabelに接続されていなければいけません。そのLabelのテキストはNotificationのメッセージがセットされます。その他のラベルの文字はStoryboardで設定した値しか表示できません。(!?)

StaticなNotificationのLabelと画像は変更できません。(だからStaticなのか!)

notification_design

Configuring a Dynamic Notification Interface

DynamicなNotificationのInterfaceを実装するためにはWKUserNotificationInterfaceControllerのサブクラスを作成する必要があります。

Designing Your Dynamic Interface

WKUserNotificationInterfaceControllerのサブクラスで実行時にLabel、画像、その他のオブジェクトをOutlet経由で変更することができます。
Notificationをタップすると、Watch Appが起動するので、インタラクティブなコントロールを含むべきではありません。

  • 基本的にLabel、画像、グループ、セパレータを使用してください。
  • テーブルや地図を含むこともできます。
  • ボタンやスイッチ、その他のインタラクティブなコントロールを含めないでください。

Configuring Your Dynamic Interface at Runtime

初期化後、WatchKitはあなたのNotificationInterfaceControllerの以下のメソッドにPayloadのデータを渡します。
このメソッド内で渡されたデータに対応するようにInterfaceを書き換えます。

  • Remote Notificationの場合 didReceiveRemoteNotification:withCompletion:
  • Local Notificationの場合 didReceiveLocalNotification:withCompletion:

このメソッドでInterfaceの書き換えがおわったら、WatchKitにInterfaceの準備が完了したことを伝えるために、Completionハンドラブロックを実行する必要があります。

notification_event_cycle_2x

didReceiveRemoteNotification:withCompletion:の実装例です。

- (void)didReceiveRemoteNotification:(NSDictionary *)remoteNotification withCompletion:(void(^)(WKUserNotificationInterfaceType interface)) completionHandler {
    // Get the aps dictionary from the payload.
    NSDictionary* apsDict = [remoteNotification objectForKey:apsKeyString];
    // Retrieve the title of the invitation.
    NSString* titleString = [apsDict objectForKey:titleKeyString];
    [self.titleLabel setText:titleString];
    // Extract the date and time from the custom section of the payload.
    // The date/time information is stored as the number of seconds since 1970.
    NSDictionary* customDataDict = [remoteNotification objectForKey:customDataKey];
    NSNumber* dateValue = [customDataDict objectForKey:invitationDateKey];
    NSDate* inviteDate = [NSDate dateWithTimeIntervalSince1970:[dateValue doubleValue]];
    // Format the date and time strings.
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    // Call a custom method to get the localized date format string for the user.
    // The default date format string is "EEE, MMM d".
    dateFormatter.dateFormat = [self dateFormatForCurrentUser];
    NSString *formattedDateString = [dateFormatter stringFromDate:inviteDate];
    // Call a custom method to get the localized time format string for the user.
    // The default time format string is "h:mm a".
    dateFormatter.dateFormat = [self timeFormatForCurrentUser];
    NSString *formattedTimeString = [dateFormatter stringFromDate:inviteDate];
    // Set the date and time in the corresponding labels.
    [self.dateLabel setText:formattedDateString];
    [self.timeLabel setText:formattedTimeString];
    // Set the location of the meeting.
    NSString* locationString = [customDataDict objectForKey:invitationLocationKey];
    [self.locationLabel setText:locationString];
    // Set the invitation's notes (if any).
    NSString* notesString = [customDataDict objectForKey:invitationNotesKey];
    [self.notesLabel setText:notesString];
    // Tell WatchKit to display the custom interface.
    completionHandler(WKUserNotificationInterfaceTypeCustom);
}

Testing Your Custom Interface

シミュレータでDynamicなNotificationのInterfaceをテストするには、ビルドスキームを作成します。
そして、Xcodeのテンプレートで提供されたRemoteNotificationPayload.jsonをPayloadのデータとして使用します。
詳細については以下を参照してください。
WatchKit Programming Guide: Configuring Your Xcode Project

参考

WatchKit Programming Guide: Custom Notification Interfaces

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


Apple WatchでのNotification受け取りについて

この投稿はWatchKit Advent Calendar 2014の9日目の記事です。

Notificationを受け取ったら

あなたのiOSアプリがLocal NotificationかRemote Notificationをサポートしていたら、Apple WatchもNotificationを表示します。
もし、ユーザのiPhoneにLocal NotificationかRemote Notificationが届いたら、iOSはiPhoneかApple WatchのどちらにNotificationを表示するかを決定します。
Apple WatchにNotificationが送られた場合、システムはユーザにNotificationが来たことを微かに知らせます。
(たぶん、バイブレーション)
もし、ユーザがNotificationを見ることを選んだら(たぶん、腕をあげたら)、システムはShort-Look Interfaceを表示します。
もし、ユーザがNotificationを見続けたら(しばらく腕を上げたままだったら??) ※、システムはLong-Look Interfaceを表示します。
Watch AppはNotificationをカスタマイズしていなければ、システムがデフォルトのNotification画面を用意してくれます。

※ Short-Look InterfaceをタップするとLong-Look Interfaceが表示できるようです。
(@koogawaさん情報有り難うございます!)

Short-Look Interface

notification_short_look

通知が来た時に、ユーザが最初に見る画面です。
Short-Look Interfaceはスクロールすることはできません。
デフォルトでは、アプリ名、アイコン、Notificationのpayloadに含まれるタイトルを表示します。

Long-Look Interface

notification_long_look

Long-Look InterfaceはNotificationの内容と関連するアクションボタンを表示するスクロール可能な画面です。
デフォルトでは、Apple Watchがアイコン、タイトル、メッセージを含む画面を表示します。

Long-Look Interfaceの3つの領域

sash

アプリアイコンとアプリ名が表示されます。
デフォルトでは、一部透明ですが色をカスタマイズすることもできます。

content area

Notificationの詳細な情報を表示します。
このエリアはある程度自由にカスタマイズ可能です。

bottom area

Dismissボタンとアプリで設定したいくつかのアクションボタンを表示します。
Dismissボタンはシステムが自動的に常に表示してくれます。

アクションボタンを追加する方法

まず、任意のアクション(UIMutableUserNotificationAction)を作成し、
カテゴリ(UIMutableUserNotificationCategory)の中に複数のアクションをセットします。
NSMutableSetにすべてのカテゴリをセットして、Notificationの設定(UIUserNotificationSettings)
にNSMutableSetとNotificationの属性(バッジや音の有無など)をセットします。
最後に[UIApplication sharedApplication]にNotificationの設定を登録します。

- (void)registerSettingsAndCategories {
    // Create a mutable set to store the category definitions.
    NSMutableSet* categories = [NSMutableSet set];
    // Define the actions for a meeting invite notification.
    UIMutableUserNotificationAction* acceptAction = [[UIMutableUserNotificationAction alloc] init];
    acceptAction.title = NSLocalizedString(@"Accept", @"Accept invitation");
    acceptAction.identifier = @"accept";
    acceptAction.activationMode = UIUserNotificationActivationModeBackground;
    acceptAction.authenticationRequired = NO;
    UIMutableUserNotificationAction* declineAction = [[UIMutableUserNotificationAction alloc] init];
    declineAction.title = NSLocalizedString(@"Decline", @"Decline invitation");
    declineAction.identifier = @"decline";
    declineAction.activationMode = UIUserNotificationActivationModeBackground;
    declineAction.authenticationRequired = NO;
    // Create the category object and add it to the set.
    UIMutableUserNotificationCategory* inviteCategory = [[UIMutableUserNotificationCategory alloc] init];
    [inviteCategory setActions:@[acceptAction, declineAction]
                    forContext:UIUserNotificationActionContextDefault];
    inviteCategory.identifier = @"invitation";
    [categories addObject:inviteCategory];
    // Configure other actions and categories and add them to the set...
    UIUserNotificationSettings* settings = [UIUserNotificationSettings settingsForTypes:
                                            (UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound)
                                                                             categories:categories];
    [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
}

アクションボタンをタップすると

システムはアクションを処理する方法を決定するために、登録されたUIUserNotificationActionオブジェクトを使用します。
アクションはフォアグラウンドまたはバックグラウンドで処理できます。

フォアグラウンドで処理する場合

あなたのWatch Appを起動し、メインのInterface ControllerにタップされたアクションボタンのIDを受け渡します。
WKInterfaceControllerのサブクラス(メインのInterface Controller)では、アクションを処理してはいけません。
WKInterfaceControllerのサブクラスでは、以下のメソッドを実装する必要があります。

  • handleActionWithIdentifier:forRemoteNotification:
  • handleActionWithIdentifier:forLocalNotification: methods

バックグラウンドで処理する場合

iOSアプリをバックグラウンドで起動し、以下のどちらかのメソッドで処理をします。

  • application:handleActionWithIdentifier:forRemoteNotification:completionHandler:
  • application:handleActionWithIdentifier:forLocalNotification:completionHandler:

参考

[UIApplication sharedApplication]

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


Apple Watchで初めて登場した新UI「Glance」について

この投稿はWatchKit Advent Calendar 2014の8日目の記事です。

Glanceとは

glance_lister_2x

GlanceはユーザがあなたのWatch Appから重要な情報を表示するための補助的な機能です。
(つまり、実装しなくてもいいです。)
iOSでいう、通知センターのようなものだと思っています。
1つのWatch Appにつき、最大1つのGlanceを持つことができます。
GlanceはWKInterfaceControllerオブジェクトですが、ユーザの操作を受け付けません。
タップすると、あなたのWatch Appを起動します。
Handoffで起動するWKInterfaceControllerを設定することもできます。
(詳細は以下を参照)
WatchKitでHandoff | blog.haranicle.net

Youtubeに動画がありました。
ちょうど2分ぐらいからGlanceの挙動を見ることができます。

Glanceインタフェースガイドライン

  • すぐに情報を伝えられるように設計します。大量の文字を表示してはいけません。グラフィックス、色、アニメーションを適切に使用してください。
  • 最も重要な情報に焦点を当ててください。Glanceはアプリに変わるものではありません。重要な情報のみをトリミングして表示してください。
  • インタラクティブなコントロールを含めないでください。(含めても操作できないので)
  • テーブルとマップの使用は避けたほうがいいようです。禁止はされていませんが、限られたスペースはテーブル、マップは有用ではありません。
  • 表示する情報をタイムリーにしてください。ユーザに情報を提供するために、時刻や位置情報など利用できるすべてのリソースを使用してください。

Glanceの作り方

2通りあります。

Watch Appのターゲット作成時に自動生成

まだプロジェクトにWatch Appのターゲットがない場合はこの手順でGlanceを作成できます。こっちのほうがおすすめ。

File > New > Target… >
Watch Appを選択してNext >
Choose options for your new target:でInclude Glance Sceneにチェック >
Finish

これで、以下のものが作成されます。

  • WatchKit Extensionグループ内にGlanceController.swift
  • Watch Appグループ内のStoryboard内にGlance Interface

手動でGlance作成

すでにプロジェクトにWatch Appのターゲットにある場合はこの手順でGlanceを作成できます。

  1. Watch Appのグループを右クリック > New File… > Cococa Classを選択してNext > Class: 適当なクラス名、Subclass of:WKInterfaceControllerにしてNext > TargetsでWatchKit ExtensionのみにチェックしてCreate
  2. Watch Appのグループ内にあるStoryboardにGlance Interface Controllerを追加します。
  3. StoryboardにGlance Interface Controllerと 1. で作成したクラスのファイルを紐付け

iOSシミュレータでのGlanceの起動方法

Glance起動用のSchemeを作成

簡単にGlanceを起動できるようにするために、Schemeを作成します。
Product > Scheme > Edit Scheme… >
左上で”プロジェクト名 Watch App”のSchemeを選択 >
Duplicate SchemeボタンでSchemeを複製 >
“Copy of プロジェクト名 Watch App” SchemeのRunを選択 >
Info > Executableで”Glance – プロジェクト名 Watch App.app”を選択 >
Close

Run

“Copy of プロジェクト名 Watch App” Schemeを選択してRunボタンを押すと、
Glanceが表示されます。
Scheme名は、Product > Scheme > Manage Schemes… で変更できます。

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


Apple Watch Appのライフサイクルまとめ

この投稿はWatchKit Advent Calendar 2014の7日目の記事です。

前提知識

Watch Appのアーキテクチャ

Apple Watch側にStoryboardやリソースファイルを置いて、iPhoneのWatchKit Extension側で処理を行います。
UIの更新などApple WatchとiPhone間の通信はWatchKitが行います。

app_communication_2x
(via WatchKit Programming Guide: Watch App Architecture)

Watch Appのライフサイクル

ユーザがApple WatchであなたのWatch Appを起動すると、自動的にiPhone側のWatchKit Extensionを起動します。
ユーザの操作が終わると、iOSはWatchKit Extensionの実行を中断します。
つまり、WatchKit Extensionは、ユーザがApple WatchであなたのWatch Appを操作中の時のみ実行できます。

launch_cycle_2x
(via WatchKit Programming Guide: Watch App Architecture)

WKInterfaceControllerでのライフサイクルハンドリング

下記図のとおりです。

watch_app_lifecycle_simple_2x
(via WatchKit Programming Guide: Watch App Architecture)

initWithContext:

WKInterfaceControllerが作成されて、最初に呼ばれるメソッドです。
データのロードや、ラベル、画像、テーブルなどのオブジェクトを更新するために使用します。
contextでHandoffや他の画面から値を受け取ることができます。

willActivate

ユーザにUIが表示されたタイミングで呼ばれるメソッドです。
UIの更新や、UIがアクティブなときだけ発生するようなタスクの実行に使用します。
例えば、タイマーのセットや、アニメーションの開始、ビデオのストリーミング開始(ぇ、そんなのできたっけ?)などです。

didDeactivate

ユーザがApple Watchであなたのアプリを操作することをやめると呼ばれるメソッドです。
クリーンアップの処理を行います。
例えば、タイマーの停止や、アニメーションの停止、ビデオのストリーミング停止などです。
次にwillActivateが呼ばれるまでの間は、UIに値をセットしても無視されます。

複数画面ある場合は?

実験してみました。
First、Second、Thirdという名前のWKInterfaceControllerを作成し、
First->Second->Third->Second->Firstの順に画面遷移し、ライフサイクルハンドリングするメソッドでログを吐いてみました。

以下ログ。

FirstのinitWithContext
FirstのwillActive
SecondをPush
SecondのinitWithContext
SecondのwillActivate
FirstのdidDeactivate
ThirdをPush
ThirdのinitWithContext
ThirdのwillActivate
SecondのdidDeactivate
ThirdをPop
SecondのwillActivate
ThirdのdidDeactivate
SecondをPop
FirstのwillActivate
SecondのdidDeactivate

次の画面がwillActivateになったら、自身がdidDeactivateになるようです。

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


WKInterfaceDeviceクラスの全プロパティ・メソッド解説

この投稿はWatchKit Advent Calendar 2014の6日目の記事です。

WKInterfaceDevice

Apple Watchのデバイス情報を取得するためのクラスです。
以下すべてのメソッドとプロパティの解説です。

Getting the Shared Device Object

  • currentDevice
    デバイスオブジェクトを取得します。

Getting the Screen Information

  • screenBounds
    画面のサイズのCGRectが取得できます。

  • screenScale
    画面のスケールが取得できます。Apple Watchは2.0です。

Getting the Device Settings

  • currentLocale
    ロケールの設定をNSLocaleで取得できます。
    NSLocaleについてはこちら参照。
    NSLocale Class Reference

  • preferredContentSizeCategory
    ユーザはOSの設定でアプリに表示するアプリのサイズ(大きめ、小さめ)を要求できます。
    システムがreturnしたフォントオブジェクトはこの設定に応じて自動的にリサイズされます。
    コードからフォントオブジェクトを要求する場合、適切なサイズのフォントオブジェクトを要求するために、このプロパティの値を使用します。
    このプロパティの値については以下を参照してください。
    Content Size Category Constants – UIApplication Class Reference

Managing Image Caches on the Device

画像をキャッシュできるようです。
@tidさんの詳しい解説があるので省略します。
Swift – WatchKitでWatchに画像をキャッシュする – Qiita

まとめ

シンプル。必要最低限という感じですね…

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


WKInterfaceObjectクラスの全プロパティ・メソッド解説

この投稿はWatchKit Advent Calendar 2014の5日目の記事です。

WKInterfaceObjectとは

WatchKitに含まれるすべてのUIパーツのベースとなるクラスです。
つまり、WKInterfaceObjectを理解すれば、WatchKitのUIパーツすべて理解できます。たぶん。

WKInterfaceObjectはなんと、継承したり、コードでインスタンスを作ったりはできません。
すべてのUIパーツはStoryboardで作成する必要があります。
コードからUIパーツを弄りたい場合は、以下のようにしてStoryboardから参照します。

@property (weak, nonatomic) IBOutlet WKInterfaceButton* myButton;

詳しくはリファレンスを参照してください。

Hiding and Showing the Object

  • setHidden:
    iOSのUIViewと同じです。インスタンスを作ることはできないけど、隠すことはできるみたいです。

  • setAlpha:
    アルファを0.0から1.0の間で指定できます。0.0だと完全に透明で見えなくなります。

Getting the Property Name

  • interfaceProperty
    Storyboard上のオブジェクトとコードで扱うオブジェクトを対応させるためにWatchKitが内部的に使用する値のようです。
    開発者がこの値を直接使用する必要はないそうです。

Changing an Object’s Size

  • setWidth:
    Widthを指定します。
    0.0をセットすると、無視されてStoryboardの値が使用されます。

  • setHeight:
    Heightを指定します。
    0.0をセットすると、無視されてStoryboardの値が使用されます。

Configuring the Accessibility Attributes

  • setAccessibilityLabel:
    オブジェクトの簡潔な説明をセットします。

  • setAccessibilityHint:
    ユーザがオブジェクトを操作した時に何が起こるかの説明をセットします。

  • setAccessibilityLabel:
    オブジェクトの値をセットします。(スライダーやスイッチで使用する?)

そもそもAccessibilityとは

iOSの設定でアクセシビリティという項目がありますが、
VoiceOverやAssistiveTouchなど障害者の方向けの機能(?)などが
含まれています。

下記ページに詳しく書かれています。

iOSアプリ開発とアクセシビリティ対応について | Technology-Gym

Accessibilityに値をセットするとどんな挙動になるの?

試しにSliderを作って、適当な値をセットし、アクセシビリティインスペクタで確認してみましたが何も起きませんでした。
Apple Watchにはどのようなアクセシビリティの機能があるんでしょうか…

まとめ

以上、WKInterfaceObjectの全メソッド解説でした。
全体的にsetterしかないんですね。
Accessibilityがどんな挙動になるんでしょうか。楽しみです。

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


WatchKitでpush画面遷移

この投稿はWatchKit Advent Calendar 2014の4日目の記事です。

Apple WatchでのPush画面遷移

Apple Watchでは以下の3種類の画面遷移があるようです。

  • push
  • modal
  • page (正式名称はわかりませんがページをめくるような遷移)

今回はpushのお話です。

リファレンス

今回もリファレンスを確認してWKInterfaceControllerに怪しいAPIがありました。

WKInterfaceController

WKInterfaceController Class Reference

以下の3つのAPIが関係ありそうです。
* pushControllerWithName:context:
* popController
* popToRootController

実装

Storyboard

以下のようにします。
storyboard

緑の四角形はボタンです。

コード

FirstInterfaceController.swift

// pushSecondボタン押下時の処理
@IBAction func onPushSecondButtonPushed() {
    pushControllerWithName("secondInterfaceControllerId", context: "gotoSecond!!")
}

引数nameには遷移先のInterfaceControllerのIdentifierを指定します。
IdentifierはStoryboardで設定します。
contextには遷移先のInterfaceControllerに受渡したい値を指定します。
contextはAnyObject!なのでなんでも受け渡せそうです。
contextはnilを指定することもできますが、おすすめはしません。(…とリファレンスに書いてありますが理由はよくわかりません。)

WatchKitにはiOSでいうNavigationControllerのようなものはありません。
pushControllerWithName:context:を実行するだけでpush画面遷移できます。iOSに慣れているとなんだか不思議。

SecondInterfaceController.swift

import WatchKit
import Foundation

class SecondInterfaceController: WKInterfaceController {
    
    // 初期化処理
    override init(context: AnyObject?) {
        super.init(context: context)
        NSLog("%@ init context:%@", self, context as String)
    }
    
    // pushThirdボタン押下時の処理
    @IBAction func onPushThirdButtonPushed() {
        pushControllerWithName("thirdInterfaceControllerId", context: "gotoThird!!")
    }
    
    // popボタン押下時の処理
    @IBAction func onPopButtonPushed() {
        popController()
    }
}

init実行のタイミング、つまりFirstInterfaceControllerのpushSecondボタン押下時に以下の様なログが表示されます。

2014-12-03 23:52:43.805 WatchKitSample WatchKit Extension[12014:2877844] <WatchKitSample_WatchKit_Extension.SecondInterfaceController: 0x7fe99c200080> init context:gotoSecond!!

contextで値を受け渡せていることがわかりますね。

popController()を実行すると前の画面に戻ることができます。
ただ、popで画面遷移すると、iOSのNavigationControllerのように自動的に左上に戻るボタンが表示されます。
戻るボタンは自分で実装する必要はなさそうです。

ThirdInterfaceController.swift

import WatchKit
import Foundation

class ThirdInterfaceController: WKInterfaceController {
    
    override init(context: AnyObject?) {
        super.init(context: context)
        NSLog("%@ init context:%@", self, context as String)
    }
    
    @IBAction func onPopButtonPushed() {
        popController()
    }
    
    @IBAction func onPopToRootButtonPushed() {
        popToRootController()
    }
}

popToRootController()を実行すると、FirstInterfaceControllerに戻れます。
一気に戻れるので便利ですね。

まとめ

以上、push画面遷移の解説でした。とっても普通。

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


WatchKitで文字入力UIのTextInputControllerを表示する

この投稿はWatchKit Advent Calendar 2014の3日目の記事です。

Apple Watchで文字入力できるの?

Apple Watchを紹介しますの動画の2:19あたりにこんな映像があります。

textinputcontroller

このUIで以下の方法で文字入力出来そうです.
* リストから文字列を選ぶ
* Smart Replies (なんだこれ)
* 音声入力

リファレンス

今回もリファレンスを確認してWKInterfaceControllerに怪しいAPIがありました。

WKInterfaceController

WKInterfaceController Class Reference

以下の2つのAPIが関係ありそうです。

メソッド名がいかにも..

実装

早速実装してみました。
StoryboardでWKInterfaceControllerの画面にボタンを2つ配置し、
ボタン押下時の処理を以下のようにしました。
これで、片方のボタン押下時には文字入力のUIが表示されて、
もう片方のボタン押下時には文字入力のUIが閉じるはず!

@IBAction func onOpenButtonPushed() {
    presentTextInputControllerWithSuggestions(["aaa", "bbb", "ccc"], completion: {(str) -> Void in println(str)} )
}

@IBAction func onCloseButtonPushed() {
    dismissTextInputController()
}

実行結果

ボタンを押しても何も起きませんでした。

たすけてStack Overflow

The presentTextInputControllerWithSuggestions:completion: method of WKInterfaceController is not currently supported in iOS Simulator.
swift – Text Input Controller WatchKit – Stack Overflow

…はい。

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


WatchKitでHandoff

この投稿はWatchKit Advent Calendar 2014の2日目の記事です。

前提知識

Watch Appの画面

以下の3種類があります。
WatchKitのページの中央あたりに写真が載っています。

WatchKit Apps

アプリの画面です。WKInterfaceControllerクラスを継承して作成します。

Glances

read-onlyな情報を提示する画面です。WKInterfaceControllerクラスを継承して作成します。

Actionable Notifications

通知画面です。WKUserNotificationInterfaceControllerクラスを継承して作成します。WKUserNotificationInterfaceControllerクラスはWKInterfaceControllerクラスを継承しています。

Handoffとは

HandoffはiOS8およびOSXv10.10で新たに導入された機能で、同じユーザに結びつけられた複数のデバイス間で、操作内容(アクティビティ)を引き継ぐことができます。
(via Handoffプログラミングガイド)

WatchKitでHandoff

そもそも、そんなことできるの?

WatchKit – Apple Developerの動画の25:20あたりでUse Handoff APIと書かれているのできっと使えるはず!

今すぐためせる?

後述しますが、一部シミュレータで試せます。

リファレンスを読み解く

リファレンスを確認すると、WKInterfaceControllerにそれっぽいAPIがありました。
(長いのでとりあえず動かしたい人は、”実装してみる”まで読み飛ばしてOKです。)

WKInterfaceController

WKInterfaceController Class Reference

以下の2つのAPIがHandoffに関係ありそうです。

actionForUserActivity:context:

リファレンス

リファレンス日本語訳

Return Value
  • ルートのInterfaceControllerのかわりに表示する画面のInterfaceControllerのIdentifier。
  • nilを返すとルートのInterfaceControllerが表示される。
Discussion
  • このメソッドを実装して、Handoffからアプリが起動された時に、どのInterfaceControllerを表示するかをWatchKitに伝えるために使用します。
  • Glanceをタップすると、Glanceに対応するアプリが起動します。
  • 通常であれば、ルートのInterfaceControllerが表示されます。
  • しかし、GlanceでupdateUserActivity:userInfo:メソッドを呼び出すと、システムが起動サイクルの早期に他のInterfaceControllerのIdentifier※を返すチャンスをくれます。
  • もし、Glanceが有効なIdentifierを返したら、システムはルートの画面の代わりにIdentifierに対応するInterfaceControllerを表示します。
  • このメソッドのデフォルト実装は何もしません。このメソッドをオーバーライドするとき、superを呼び出してはいけません。

※ IdentifierはStoryboardで設定します。

解説

GlanceでupdateUserActivity:userInfo:メソッドを実行すると、アプリ起動時に表示する画面を切り替えられるようになります。
userInfoに好きな情報を入れられるので、アプリ起動時に任意の画面でuserInfoの情報を表示することができそうです。

updateUserActivity:userInfo:

リファレンス

リファレンス日本語訳

Discussion
  • Glanceでは、このメソッドを呼び出して、表示している内容についての情報をuserInfoとして提供してください。ユーザがGlanceをタップするとアプリにコンテキスト情報が送信されます。
  • このメソッドを呼び出すと、アプリにアクティビティを送信します。システムがiPhoneに情報を配信し、iPhoneが他のデバイスに情報を伝播します。
  • InterfaceControllerのコード実行中はいつでもこのメソッドを呼び出すことができる。

解説

メソッド名の通り、アクティビティの更新ができるようです。

実装してみる

前置きが長くなりましたが、いよいよWatchKitでHandoffを使うアプリを実装してみましょう。
リファレンスを読み解いた結果、2種類のパターンがあることがわかりました。

Storyboard

以下のようになってるとします。(XcodeのスクリーンショットはNDAなので…)
storyboard

[パターン1] iPhoneのアクティビティをAppleWatchで継続する場合

Glanceからアプリを起動します。
このパターンはシミュレータで動くことを確認できます。

処理の流れ

以下の様な流れになります。

p1

  1. GlanceでupdateUserActivity:userInfo:を実行する。
  2. ユーザがGlanceをタップする。
  3. Firstに実装したactionForUserActivity:context:がアプリ起動時に表示したいInterfaceControllerのIdentifier(つまりSecondのIdentifier)を返す。
  4. システムがIdentifierに対応するInterfaceController(つまりSecond)を表示する。

GranceController.swift

どこかでupdateUserActivity:userInfo:を実行します。
今回は、initで実行しました。

override init(context: AnyObject?) {
    super.init(context: context)
    NSLog("%@ init", self)    
    updateUserActivity("net.haranicle.watchHandoff.sample", userInfo: ["hoge":"piyo"])
}

FirstInterfaceController.swift

actionForUserActivity:context:を実装して、SecondのIdentifierをreturnします。

override func actionForUserActivity(userActivity: [NSObject : AnyObject]?, context: AutoreleasingUnsafeMutablePointer<AnyObject?>) -> String? {
    NSLog("%@ actionForUserActivity", self)
    
    return "secondInterfaceControllerId"
}

これだけでGlanceをタップすると自動的にSecondが表示されるようになりました!
(一瞬Firstが表示されます。)

動かないときは…

iOSシミュレータでiCloudにログインしてみてください。
Glanceの起動方法がわからない場合はググって!(NDAこわい)

[パターン2] AppleWatchのアクティビティをiPhoneで継続する場合

先に断っておきますが、このパターンは動きませんでした。
WatchKitのバグなのか、僕のコードが悪いのかわかりませんが。
以下は予想でしかないです。間違っていたら指摘してください。

処理の流れ

p2

  1. First実行中の任意のタイミング(ボタン押下時にした)でupdateUserActivity:userInfo:を実行する。
  2. システムがiPhoneに情報(アクティビティ)を配信し、iPhoneが他のデバイスに情報を伝播します。
  3. ユーザがiOSデバイスのロック画面のHandoffからiOSアプリを起動します。
  4. HandoffでiOSアプリ起動時にで継続の処理(application:willContinueUserActivityWithType)が実行されます。

FirstInterfaceController.swift

ボタン押下時にupdateUserActivity:userInfo:を実行しましたが、ボタン押下時にWatch画面に回るポンデリングが表示されっぱなしになりました。
なにか間違ってるんでしょうか..? このパターンの検証は諦めました。

@IBAction func onButtonPushed() {
    updateUserActivity("net.haranicle.watchHandoff.buttonPushed", userInfo: ["hoge":"piyo"])
}

まとめ

今回は[パターン1]iPhoneのアクティビティをAppleWatchで継続する場合はWatchKitでもHandoffが動くことがわかりました。
早く実機でためしたい!!

参考

Getting started with Handoff

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


potatitips#11でiPhoneだけでデバッグする方法について発表してきた

potatitips#11

無謀にもpotatitips#11で発表してきました。
iOSの分は@yimajoさんが丁寧にまとめてくれています。

クックパッド社で #potatotips 第11回に参加してきた!あのApple Watch用SDKのTipsもあったよ! | 株式会社キュリオシティソフトウェア

(さらに…)


YosemiteにしたらCocoaPodsがうごかなくなった

エラーメッセージ

$ pod install
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/fileutils.rb:245:in `mkdir': Permission denied - /Library/Ruby/Gems/2.0.0/extensions/universal-darwin-14 (Errno::EACCES)
	from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/fileutils.rb:245:in `fu_mkdir'
	from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/fileutils.rb:219:in `block (2 levels) in mkdir_p'
	from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/fileutils.rb:217:in `reverse_each'
	from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/fileutils.rb:217:in `block in mkdir_p'
	from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/fileutils.rb:203:in `each'
	from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/fileutils.rb:203:in `mkdir_p'
	from /Library/Ruby/Site/2.0.0/rubygems/ext/builder.rb:210:in `write_gem_make_out'
	from /Library/Ruby/Site/2.0.0/rubygems/ext/builder.rb:132:in `build_error'
	from /Library/Ruby/Site/2.0.0/rubygems/ext/builder.rb:171:in `rescue in build_extension'
	from /Library/Ruby/Site/2.0.0/rubygems/ext/builder.rb:156:in `build_extension'
	from /Library/Ruby/Site/2.0.0/rubygems/ext/builder.rb:198:in `block in build_extensions'
	from /Library/Ruby/Site/2.0.0/rubygems/ext/builder.rb:195:in `each'
	from /Library/Ruby/Site/2.0.0/rubygems/ext/builder.rb:195:in `build_extensions'
	from /Library/Ruby/Site/2.0.0/rubygems/specification.rb:1436:in `block in build_extensions'
	from /Library/Ruby/Site/2.0.0/rubygems/user_interaction.rb:45:in `use_ui'
	from /Library/Ruby/Site/2.0.0/rubygems/specification.rb:1434:in `build_extensions'
	from /Library/Ruby/Site/2.0.0/rubygems/stub_specification.rb:60:in `build_extensions'
	from /Library/Ruby/Site/2.0.0/rubygems/basic_specification.rb:56:in `contains_requirable_file?'
	from /Library/Ruby/Site/2.0.0/rubygems/specification.rb:925:in `block in find_inactive_by_path'
	from /Library/Ruby/Site/2.0.0/rubygems/specification.rb:924:in `each'
	from /Library/Ruby/Site/2.0.0/rubygems/specification.rb:924:in `find'
	from /Library/Ruby/Site/2.0.0/rubygems/specification.rb:924:in `find_inactive_by_path'
	from /Library/Ruby/Site/2.0.0/rubygems.rb:185:in `try_activate'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:132:in `rescue in require'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:144:in `require'
	from /Library/Ruby/Gems/2.0.0/gems/xcodeproj-0.17.0/lib/xcodeproj/ext.rb:4:in `<top (required)>'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
	from /Library/Ruby/Gems/2.0.0/gems/xcodeproj-0.17.0/lib/xcodeproj.rb:30:in `<top (required)>'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
	from /Library/Ruby/Gems/2.0.0/gems/cocoapods-0.33.1/lib/cocoapods.rb:2:in `<top (required)>'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
	from /Library/Ruby/Gems/2.0.0/gems/cocoapods-0.33.1/bin/pod:32:in `<top (required)>'
	from /usr/bin/pod:23:in `load'
	from /usr/bin/pod:23:in `<main>'

解決法

これでなおった。

$ sudo gem uninstall cocoapods && sudo gem install cocoapods

Swift1.1で黒魔術「Method Swizzling」

Method Swizzlingとは

既存のメソッドを自作のメソッドに差し替える黒魔術です。
Objective-Cで実現する方法については、こちらのページがわかりやすいです。

既存クラスのメソッドを入れ替える(Method Swizzling)

Swiftで試す

Swiftでもclass_getInstanceMethod()とmethod_exchangeImplementations()が動くので
単純に書き換えるだけで動きました。
以下は、UIViewControllerのviewDidLoadで試したコードです。
viewDidLoadのタイミングでログを吐くようにしています。

extension UIViewController {
    func loggingViewDidLoad() {
        println("\(self.dynamicType) viewDidLoad")
    }
    
    class func switchLoggingMethod () {
        let fromMethod = class_getInstanceMethod(UIViewController.self, "viewDidLoad")
        let toMethod = class_getInstanceMethod(UIViewController.self, "loggingViewDidLoad")
        method_exchangeImplementations(fromMethod, toMethod);
    }
}

ポイント

extension

Swiftにはカテゴリがないので、extensionで実現しています。やってることはObjective-Cのカテゴリと同じです。

class_getInstanceMethod

第2引数がただの文字列です。ここが一番はまりました。
ちなみにSwift1.0では以下のような書き方になるようです。

let fromMethod = class_getInstanceMethod(UIViewController.self, Selector.convertFromStringLiteral("viewDidLoad"))

すべてのクラスで使えるわけではない

これ重要。
NSObjectを継承していないクラスでは使用できません。

参考

Swift funtime

サンプルコード

haranicle/SwiftMethodSwizzling


FMDBで使うSQLiteをSQLCipherで暗号化する

アプリで使用するSQLiteは書き換えられる

iFunBoxというツールを使用すると簡単にアプリで生成したファイルを閲覧したり、編集できたりしてしまいます。
iFunBox for Windows | File Manager, Browser, Explorer, Transferer for iPhone, iPad and iPod Touch

こちらに詳しく書かれています。
iPhoneアプリの初歩的なデータ書き換え・チート方法と、それを防ぐ方法 | Lancork

FMDB + SQLCipher でSQLite暗号化

SQLCipherというSQLite暗号化ライブラリを使用すると、SQLiteのファイルを暗号化し
閲覧・編集できないようにできます。
FMDBでSQLiteを使用している場合は、SQLCipherを簡単に組み込むことができます。

SQLCipherは有料で結構なお値段しちゃいます。
Buy SQLCipher – Zetetic

でも、SQLCipher Community Editionであれば、無料だし、BSDライセンスで利用できます。
SQLCipher – Zetetic

SQLCipher Community Editionは自分でビルドしないといけませんが、俺達にはCocoaPodsがついてるんだぜ。

FMDB + SQLCipher 導入方法

CocoaPods

Podfileに以下を追記

pod 'FMDB/SQLCipher'

ターミナルでpod install

$pod install

Project設定

project_settings

Project > Build Settings > Other C Flagsに “-DSQLITE_HAS_CODEC” を追記

以上で導入完了です。

使い方

ほぼ通常のFMDBと同様ですが、毎回FMDatabaseをOpenした直後に
setKeyで暗号化のキーをセットしてあげてください。

FMDatabase* db = [FMDatabase databaseWithPath:@"path/to/dbfile"];
NSString* sql = @"INSERT INTO nums (num) VALUES (?)";
[db open];
[db setKey:@"dbfile_encrypt_key"]; // 暗号化のキーを設定
[db executeUpdate:sql, [NSString stringWithFormat:@"%ld", num]];
[db close];

サンプルコード

haranicle/DadamoreApp
のFmdbDaoを見てください。
ビルドする前にpod installしてください。

ハマりポイント

FmdbDaoの@propertyにFMDatabase*型のインスタンスを持たせて、
いろんなメソッド(INSERTするメソッドとかSELETするメソッドとか)で使いまわしていたら、
以下の様なエラーが出て、DBにアクセスできませんでした。

26 "file is encrypted or is not a database"

各メソッドで毎回FMDatabase#databaseWithPathして、setKeyをするようにしたら
正常にDBにアクセスできて、暗号化も動作するようになりました。

参考

FMDB with SQLCipher Tutorial | Guilmo


第58回 Cocoa勉強会関西でXcode6のFramework Templateについて話した

発表内容

Xcode6からiOS用のFrameworkを作るためのプロジェクトのテンプレートが追加されました。
(OSX用のは前からあった)
iOS用のFrameworkを作るためにがんばってみたことと、
EmbedFrameworkを用いた複数ターゲット間でコードの共有方法と、
おまけでLive Renderingについて話しました。

(さらに…)


第58回 Cocoa勉強会関西いってきたよ

ATND

yidev 第16回勉強会 : ATNDの懇親会で@studioshinさんにハメられて、@dealforestさんと一緒に、これに行ってきました。

第58回 Cocoa勉強会関西 – iOS/Macアプリ開発勉強会 : ATND

Togetter

第58回 Cocoa勉強会関西 – Togetterまとめ

(さらに…)


新iTunes Connect用のスクリーンショット作成の苦行をなんとかする

新iTunes Connect

こんにちは。haranicleです。
iTunes Connectが新しくなって、iPhone6/6Plus対応のアプリを提出しようとすると、
4.7inch,5.5inch用のスクリーンショットも登録しないといけなくなりました。
スクリーンショットの枚数 * 対応言語数 * 対応機種数 のスクリーンショットを
作らないといけないのはつらすぎる。

スクリーンショットのサイズ

各端末ごとに用意しないといけないスクリーンショットのサイズです。

画面サイズ Width Height
3.5inch 640 960
4inch 640 1136
4.7inch 750 1334
5.5inch 1242 2208

4inchから5.5inchまではほぼ同じ比率なので、5.5inch用のスクリーンショットだけ作って
あとは自動で生成されるようにしたい。

Shell Script

前置きが長くなりましたが、5.5inch用のスクリーンショットから自動で他の画面サイズの
スクリーンショットを作ってくれるスクリプトを作りました。

A shell script to create AppStore screen shots.

使い方

  • 事前にImageMagickをインストールしておく
  • 5.5inch用のスクリーンショットを作って、ファイル名の最後に1から5をつけておく。
    (例) ss1.png, ss2.png … ss5.png
  • Shell Scriptをコピーして「適当な名前.sh」として保存
  • 適当な名前.shのsettingsのfileNamePrefixを数字なしのファイル名に書き換える。
    (例) fileNamePrefix=”ss”
  • $chmod +x 適当な名前.sh
  • $./適当な名前.sh
  • destディレクトリが作成されて完成!

お試しあれ


Xcode6でAppExtensionなアプリの提出でハマった

こんにちは。haranicleです。
そういえば、blogもっていたので、たまにはメモ。

AppExtension(Custom Keyboard)がついてるアプリをリリースしようとして、
XcodeでArchiveしてOrganizerでValidateしようとすると、こんなエラーが…

iTunes Store operation failed.
Bad bundle identifier. The bundle identifier ‘net.haranicle.myapp’ of the application extension ほげほげふがふがなんかのパス/MyKeyboard.appex should extend the dotted path of the bundle identifier of its containing application(‘net.haranicle.myapp’).

解決策

単刀直入に解決策。

Containing AppとApp Extensionは別BundleID、AppID、Provisioning Profile

BundleIDは↑のエラーメッセージにあるように以下のようにしないといけません。

Containing App: “com.mycompany.appname”
App Extension: “com.mycompany.appname.extensionname”

で、Bundle IDが異なるのでApp IDも別です。iOS Dev CenterでそれぞれのAppIDを作りましょう。
App IDが違うので、Provisioning Profileも別ですApp IDと同様にそれぞれのものを作りましょう。
最後にそれぞれのターゲットに対応したProvisioning Profileを設定して完成!
無事提出できたら、全裸でWaiting for Reviewしましょう。


yhios#32開催したよ

こんにちは。haranicleです。
5/17にミューザ川崎でyhios#32を開催しました。

ATND

これ
横浜へなちょこiOS勉強会#32 : ATND

(さらに…)


SHUIKitBlocksが便利なのでBlocksKitから乗り換えた

こんにちは。haranicleです。
この前の#yhiosでBlocksKitつかってるひとーって聞いてみたら、10人中2人だけで驚きました。
みんな、どうやってUIAlertViewのコード書いてるんだろう。

(さらに…)


pod installでMERGE_HEAD existsなエラーが出る

こんなエラーが出た。

[!] Pod::Executable pull --no-rebase --no-commit

You have not concluded your merge (MERGE_HEAD exists).

Please, commit your changes before you can merge.

このコマンドを叩いたらなおった。

pod repo remove master
pod setup

めでたしめでたし。

ソース
pod install often complaining about MERGE_HEAD existing · Issue #2024 · CocoaPods/CocoaPods · GitHub


同じUIViewを2回addSubViewするとどうなるんだっけ

試した

// 青
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(10, 100, 300, 100)];
view1.backgroundColor = [UIColor blueColor];
[self.view addSubview:view1];

// グレー
UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(10, 300, 300, 100)];
view2.backgroundColor = [UIColor grayColor];
[self.view addSubview:view2];


UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"logo"]];
[view1 addSubview:imageView]; // 青にaddSubView
[view2 addSubview:imageView]; // グレーにaddSubView

こうなった
addSubView

後勝ちなのね。


iOSモテコーデ術Xcode5.1対応

こんにちは。haranicleです。
きれいめ系iOSプログラマのためのモテXcode5コーデ術で書いたスクリプトがXcode5.1で動かなくなっていたから、なおした。

(さらに…)


yhios#31開催したよ

こんにちは。haranicleです。
3/15にミューザ川崎でyhios#31を開催しました。

ATND

これ横浜へなちょこiOS勉強会#31 : ATND

(さらに…)


BlocksKit2.0の導入でハマったお話

こんにちは。haranicleです。
みんなだいすきBlocksKitの導入でハマったのでメモ。

(さらに…)


yhios#30開催したよ

はじめまして。haranicleです。
1/25にミューザ川崎でyhios#30を開催しました。
川崎での開催は初です。

ATND

これ横浜へなちょこiOS勉強会#30 : ATND

(さらに…)