Jeffsuke is not a pen.

🏊‍♂️🚴‍♂️🏃‍♂️💻📱

Today Extensionを実装してみた。

Today Extensionはウィジット

Today Extension from WWDC
Today ExtensionはiOS8から導入されたウィジットを通知画面に設置する機能です。アプリの機能を拡張するExtensionの一つです。あまりに情報が少なくてハマったので、ブログに書いておきます。 Appleのドキュメントが一般公開されているので、詳しい情報は以下参照して下さい。

App Extension Programing Guide

この記事も参考にしました、 【iOS8】App Extension の実装方法 その1:ActionAdd Star

*以下はXcode6 beta3での検証結果です。画像はApp Extension Programing Guideから拝借してものです。

実装手順

  1. Today Extensionターゲットを作成
  2. Today ExtensionのViewの生成
  3. Info.plistを編集
  4. アプリ上でのコードの再利用:Framework化
  5. DBの共有:App Groupの作成
  6. データのアップデート処理
  7. Today Extensionからアプリを開く
  8. テストの実施

1. Today Extensionターゲットを作成

File > New > Target > Application Extension > Today Extensionを選択します。設定したExtensionの名前のディレクトリと、そのテストが生成されるます。生成される中身は以下のとおりです。

  • info.plist
  • .storyboardファイル
  • ViewController.m, ViewController.h

プロジェクトファイルを確認すると、Extensionとそのテストのターゲットができていることが確認できます。

Set up on Xcode6

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にしたがって設計されていると、依存関係が邪魔することなく必要なファイルだけをインポートできます。設計大事ですね。

Embedded Framework

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を定義し、共通領域に保存します。

  1. プロジェクトナビゲーター > 本体アプリ > Capabilities > App Groupsから設定をオンにします。
  2. 新しいコンテナとして、"group.com.companyName.myApp"のような命名をします。他の方法として、Apple Developer Center上でも同様に設定変更ができます。この場合、設定したprovisioning fileを再度ダウンロードし、Xcode上でプロジェクトに適応する必要があります。
  3. Today Extensionのターゲットでも同様のプロセスを実施します。
  4. NSUserDefaultを読み込むときに、[[NSUserDefaults alloc] initWithSuiteName:@"group.com.companyName.myApp"]のようにインスタンスを生成します。
  5. 後は通常通り読み書きをするだけ。

この読み書きを実施するモデルを本体アプリで定義し、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が追加されたため、今後はこれが主流になってくるだろう。

f:id:jeffsuke:20140714093118p:plain

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でラクラクセットアップ

f:id:jeffsuke:20140501204050j:plain

新しい開発環境を頂いたので、セットアップすることに。 毎回セットアップするのは、プログラマーの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で再セットアップが超効率アップ。

参考文献

phinze/homebrew-cask

Boxen使ってて許されるのは2013年だけだった

MacBook Airを再インストールする際の覚え書き(アプリケーション)

brew-caskとシェルスクリプトでMacの環境構築

Xcodeにて、SchemeのデバイスがMy Mac 64-bitしか選択できなくなった時の対処法

Xcodeを使っていて、よくSchemeのデバイスがMy Mac 64-bitしか選択できなくなる。 アプリのBundleIDをXcode上で変更した時などに発生する。

また、iPhone用ビルドを実施するのは非常に簡単。

まず、Edit Schemeを立ち上げる すると、RunのExcutableがNoneになっている。 このNoneをアプリ名.appに変更する。

以上でiPhone用ビルドができるようになる。

f:id:jeffsuke:20140210210834j:plain

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からのマイグレーションツールもあり移行も割りとスムーズ。

f:id:jeffsuke:20131215031536j:plain

また、テストナビゲーターから、ワンクリックでテストケースを一つづつ実行可能になった。

テストケースは通常の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];