Today Extensionを実装してみた。
Today Extensionはウィジット
Today ExtensionはiOS8から導入されたウィジットを通知画面に設置する機能です。アプリの機能を拡張するExtensionの一つです。あまりに情報が少なくてハマったので、ブログに書いておきます。
Appleのドキュメントが一般公開されているので、詳しい情報は以下参照して下さい。
App Extension Programing Guide
この記事も参考にしました、 【iOS8】App Extension の実装方法 その1:ActionAdd Star
*以下はXcode6 beta3での検証結果です。画像はApp Extension Programing Guideから拝借してものです。
実装手順
- Today Extensionターゲットを作成
- Today ExtensionのViewの生成
- Info.plistを編集
- アプリ上でのコードの再利用:Framework化
- DBの共有:App Groupの作成
- データのアップデート処理
- Today Extensionからアプリを開く
- テストの実施
1. Today Extensionターゲットを作成
File > New > Target > Application Extension > Today Extensionを選択します。設定したExtensionの名前のディレクトリと、そのテストが生成されるます。生成される中身は以下のとおりです。
- info.plist
- .storyboardファイル
- ViewController.m, ViewController.h
プロジェクトファイルを確認すると、Extensionとそのテストのターゲットができていることが確認できます。
2. Today ExtensionのViewの生成
Today Extensionは通常のストーリーボードのViewと、view controllerで構成されています。Model層は本体アプリと共有されたもの、もしくはキャッシュされたフェッチ結果等がそれにあたります。
通常のViewと同様にストーリーボードで画面構成を生成します。通知センターは、TableView的にリストを表示しているので、ストーリーボード上にUITableViewを設置しました。いつもどおり、dataSourceとdelegateをストーリーボード上で設定。
このUITableViewをTodayExtensionで使う時に、背景色を正しく設定しないと通知センターっぽいUIにならないので注意が必要です。
ストーリーボード上で、UITableView及びUITableViewCellの背景色を透明にするか、以下のコードで明示的に透明にする必要があります。
必然的に黒背景になるので、その他のUIパーツ、UIButtonやUILabelは白っぽい文字でトンマナを合わせる必要があります。
3. Info.plistを編集
通知センターのタイトルを変更します。Bundle display nameにヘッダー部分に表示したい名称を入れます。 NSExtensionPointIdentifierをcom.company.appName.extensionのように変更。 その他は、通常のInfo.plistと同様に変更を加えていきます。
4. アプリ上でのコードの再利用
4-1. Framework化
App Extensionはアプリとは別のサンドボックスになっており、直接お互いのコードやDBにアクセスすることはできません。また結果的に別のアプリとして動くので、本体アプリは起動していないが、Extensionは動いている状態もありえます。そこで、コードを効率的にシェアする仕組みが必要です。そう、Frameworkです。Xcode6からはiOSアプリでもEmbeded frameworkを作ることができます。
File > New > Target > Framework & Library > Cocoa Touch FrameworkからFramework targetを生成します。Extensionにて使用したいファイルをBuild PhasesのCompile Sourcesに追加していきます。
この時、プロジェクトがMVCにしたがって設計されていると、依存関係が邪魔することなく必要なファイルだけをインポートできます。設計大事ですね。
4-2. CocoaPodsの利用(2014/08/02追記
Today ExtensionでCocoaPodsを利用する場合は、対象となるターゲットをPodfileに追記する必要があります。
Podfile Syntax Reference
target :test do pod 'OCMock', '~> 2.0.1' end
5. DBの共有:App Groupの作成
モデル部分の共有は、アプリ本体とExtensionどちらともアクセスできる領域にデータを保存する必要があります。今回はNSUserDefault
を使いました。
NSUserDefault
を使う時、通常はstandardUserDefaults
を使いますが、App Groupを定義し、共通領域に保存します。
- プロジェクトナビゲーター > 本体アプリ > Capabilities > App Groupsから設定をオンにします。
- 新しいコンテナとして、"group.com.companyName.myApp"のような命名をします。他の方法として、Apple Developer Center上でも同様に設定変更ができます。この場合、設定したprovisioning fileを再度ダウンロードし、Xcode上でプロジェクトに適応する必要があります。
- Today Extensionのターゲットでも同様のプロセスを実施します。
NSUserDefault
を読み込むときに、[[NSUserDefaults alloc] initWithSuiteName:@"group.com.companyName.myApp"]
のようにインスタンスを生成します。- 後は通常通り読み書きをするだけ。
この読み書きを実施するモデルを本体アプリで定義し、Embedded Frameworkに含めると共通化できて良さそうです。
6. データのアップデート処理
Today Extensionは、以下のメッソドを呼び、定期的にアップデートされます。その時、データ取得の成否をNCUpdateResult
として渡す必要があります。
7. Today Extensionからアプリを開く
URL schemeを使います。下の記事が参考になりました。
Custom URL Schemeの処理をシンプルに書く
8. テストの実施
Today Extensionは本体とは別のサンドボックスのため、実機やシミュレータを用いたテストでも何点かハマりました。
- ビルド時のターゲットをToday Extensionのものにしないと、ログが表示されない。
- 本体アプリターゲットでビルドしないと、必要なデータを保存できない。
- beta版だからか、シミュレータが不安定。よく落ちる。
- Today Extensionのストーリーボードに変更を加えた後、クリーン後ビルドしないと、UIが変更されない。
ユニットテストは通常と同様の書き方で問題なかったです。
まとめ
iOS8では簡単にウィジットが作れる。
2014/08/02追記
Facebook GroupでCocoaPods導入についてコメントを頂きましたので、追記しました。 https://www.facebook.com/groups/ios.dev.jp/permalink/768435093177874/
SwiftでUIBlurEffect実装してみた
SwiftでUIBlurEffectを実装してみた。
iOS7の登場と共に複数のライブラリが出現した。
iOS8では、動的にぼかしエフェクトを生成できるUIBlurEffectが追加されたため、今後はこれが主流になってくるだろう。
import UIKit class BlurEffectViewController: UIViewController { @IBOutlet var image: UIImageView override func viewDidLoad() { super.viewDidLoad() addBlurEffect() } func addBlurEffect() { var effect = UIBlurEffect(style: UIBlurEffectStyle.Light); var effectView = UIVisualEffectView(effect: effect); let rect = UIScreen.mainScreen().applicationFrame effectView.frame = CGRectMake(0, 0, rect.width, rect.height / 3) view.addSubview(effectView); } }
えー、マジBoxen!?Boxenが許されるのは2013年までだよね!
Brewfile+brew-caskでラクラクセットアップ
新しい開発環境を頂いたので、セットアップすることに。 毎回セットアップするのは、プログラマーの3大美徳 (怠惰・短気・傲慢)に反するので、自動化することに。
OSの再インストール
再起動時にcommand+R長押し
で、復元機能を呼び出す。 OS X Lion: Mac OS X を消去して再インストールする
AppStoreからダウンロード
事前にAppStoreからXcodeをダウンロードする必要があります。
ターミナルでの準備
ターミナルで以下をインストールします。
- Xcode command line tool
- Homebrew
- RVM
- CocoaPods
- NeoBundle
以下のシェルを任意のディレクトリでsh setup.sh
するだけでオッケー。
#!/bin/sh # Xcode command line tool xcode-select --install # Homebrew ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)" # Homebrew-cask option to make link in Application directory echo 'export HOMEBREW_CASK_OPTS="--appdir=/Applications --caskroom=/usr/local/Caskroom" ' >> ~/.bash_profile #RVM, Ruby, Rails \curl -sSL https://get.rvm.io | bash -s stable --rails # gem gem update rake gem install cocoapods #### vim git clone https://github.com/Shougo/neobundle.vim ~/.vim/bundle/neobundle.vim echo 'Go to Vim and Type NeoBundleInstall'
HomebrewとHomebrew-caskで必要なアプリケーションを一括インストール
任意のディレクトリに以下のBrewfile
を作り、brew bundle
をターミナルで実行。
ちなみに、brew-caskでインストールできるアプリケーションは下記から分かる。
https://github.com/phinze/homebrew-cask/tree/master/Casks
ここに含まれていないアプリケーションをインストールしたい場合は以下を参照。 http://blog.livedoor.jp/sonots/archives/35251881.html
update upgrade tap homebrew/versions tap phinze/homebrew-cask install brew-cask install zsh install autojump install git install tig install ansible install wget install curl install jq install vim install mysql install mongodb cask install dash cask install google-chrome cask install virtualbox cask install vagrant cask install kobito cask install alfred cask install dropbox cask install evernote cask install skitch cask install github cask install clipmenu cask install bettertouchtool cask install google-japanese-ime cask install the-unarchiver cask install sublime-text cask install skype cask install slack cask install licecap
まとめ
Brewfile+brew-caskで再セットアップが超効率アップ。
参考文献
Objective-CとTDD
このエントリーは、TDD Advent Calendar 2013 の 6日目です。
ある日友人に「iOSアプリを作る人はあまりユニットテストしない」と言われた。自分もあまり書いた事はなかった。よく職場とかで言われるのは、
- ユニットテストを書く工数>受けられる恩恵
- iOSアプリはなんとなくでも作れるので、TDDとかいらない。
- テスト維持するの大変じゃん。
よし、良い機会だ、自分の為にもまとめよう。
TDDとは
そもそも、TDDって何さ。@biacさんが詳しくまとめてくれました。
ユニットテストをベースに開発を進める事だと理解している。ロジックのエラーを捉えたり、デグレードを防げたり、依存関係の少ないコードを書けたりと利点が多い。TDDを行うには依存関係の解決が大事。
これを実現する為に、ユニットテストが使われる。では、ユニットテストとは? 「 最小である1つの機能をテストする事」を指し、小さく、早く、単位ごとにテストができ非常に有用だ。
逆に、ユニットテストにもできないことはあり、その一つにUIテストがある。これはBDDの話になるので、以下の記事を見て欲しい。
iOS 向けTDD/BDDフレームワークやモックフレームワークの現状 - laiso - iPhoneアプリ開発グループ
Objective-Cでのテスト:XCTest
Xcode5でプロジェクトを作成すると、XCTestというテストフレームワークのクラスが自動生成される。また、Xcode5からはBotsと呼ばれるCIも導入された。さらに、Xcode4で使われていたOCUnitからのマイグレーションツールもあり移行も割りとスムーズ。
また、テストナビゲーターから、ワンクリックでテストケースを一つづつ実行可能になった。
テストケースは通常のObjective-Cと同様に書ける。例を以下に示す。
@interface ExampleTests:XCTestCase @end @implementation ExampleTests - (void) testExample { XCTAssertTrue(2 + 2 = 4); }
TDDで依存関係の少ないコードを書く
では、テストケースは分かったが、どうテストを書くのだろうか。以下のメソッドにTDD的な考えを導入しよう。
- (BOOL) saveIDNumberToDB:(NSNumber *)ID { JSKDataManager *dataManager = [JSKDataManager sharedInstance]; [dataManager addID:ID]; return [dataManager save]; }
まず、何かしらのシングルトンのデータマネジャーに、IDのNSNumberを渡し、保存し、その成否をリターンしている。 最小単位でテストするために、依存関係を取り除く必要がある。依存箇所をプロパティにすることを今回は考える。すると、シングルトンこの場合最小単位のテストは、
- (void)testSharedInstanceCreated { JSKViewController *viewController = [[JSKViewController alloc] init]; XCTAssert([viewController.dataManager class] == [JSKDataManager class]); }
これに合わせてコードを変更すると、
@interface JSKViewController () @property (nonatomic, strong) JSKDataManager *dataManager; @end @implementation JSKViewController - (BOOL) saveIDNumberToDB:(NSNumber *)ID { self.dataManager = [JSKDataManager sharedInstance]; [self.dataManager addID:ID]; return [self.dataManager save]; }
という形になり、依存関係の少ないきれいなコードに近づけるというわけだ。さらに、TDDで開発をしていれば、仕様変更時のテストの変更も最小限になるため、かかる工数よりも受けれる恩恵が多そうだ(もちろん出来ないことは出来ないけどね)
まとめ
今回はObjective-CとXcodeにおけるTDDについて調べた。TDDは、デグレードを防ぎ、メソッド感の依存関係を少なく記述するための指針となる。iOSアプリの世界でも、モデルコントローラー間では多くの恩恵を受けれそうだ。
サブビューのUILabelの高さに合わせて、TableViewCellの高さを変更したい時。
CGSize size = [string sizeWithFont:[UIFont boldSystemFontOfSize:11] constrainedToSize:CGSizeMake(width, 2000) lineBreakMode:UILineBreakModeWordWrap]; [lbNotice sizeToFit];