24/7 twenty-four seven

iOS/OS X application programing topics.

iPhone の画面操作を録画するライブラリを公開しました。

kishikawakatsumi/ScreenRecorder · GitHub

ScreenRecorder は iOS デバイスの画面を連続的にキャプチャして、動画に変換することで画面の操作を録画することができる機能をアプリケーションに追加します。
開発中のソフトウェアのユーザーテストなどに利用すると効果的です。

使い方

1. 以下のファイルをプロジェクトに追加します

  • Lib/SRScreenRecorder.h
  • Lib/SRScreenRecorder.m
  • Vendor/KTouchPointerWindow.h
  • Vendor/KTouchPointerWindow.m

2. 以下のフレームワークをリンクします

  • QuartzCore.framework
  • CoreVideo.framework
  • CoreMedia.framework
  • AVFoundation.framework

startRecording で録画を開始します。
デフォルトの設定は

  • バックグラウンドに入ったときに自動保存
  • 10 分ごとに自動保存、ファイルのローテート
  • 30FPSで録画
  • タッチ箇所の表示

となっています。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[SRScreenRecorder sharedInstance] startRecording];
    return YES;
}


いくつかの挙動は、設定で変更することができます。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    SRScreenRecorder *recorder = [SRScreenRecorder sharedInstance];
    recorder.frameInterval = 1; // 60 FPS
    recorder.autosaveDuration = 1800; // 30 minutes
    recorder.showsTouchPointer = NO; // hidden touch pointer
    recorder.filenameBlock = ^(void) {
        return @"screencast.mov";
    }; // change filename
    
    [recorder startRecording];
    
    return YES;
}


タッチ・ポインターの表示には @ さんの KTouchPointerWindow を利用しています。
iPhone/iPadの画面にタッチ位置を表示するためのソースコード KTouchPointerWindow を公開しました
itok/KTouchPointerWindow · GitHub

Windows 8 StoreアプリケーションでGoogleAnalyticsを使う

kishikawakatsumi/GoogleAnalytics-for-WinJS · GitHub

Windows 8の Store アプリケーションで GoogleAnalytics を使う場合、まだ公式の SDK はありませんが、サードパーティからすでにいくつかライブラリがリリースされているのでそれを使うのがいいと思います。
ただ JavaScript(WinJS) で書いた Metro Style アプリケーション用のものはまだ無いみたいなので Windows Runtime Component を使って WinJS から C# のライブラリを扱う方法でやってみました。


Google Analytics for WinRT - Home
NuGet Gallery | Google Analytics for WinRT (Windows 8 and 8.1) and Universal apps 2.6.0

↑ GoogleAnalytics にリクエストを送る Windows 8 用のライブラリはいくつかありますが、今回はソースコードも公開されていて実装もシンプルな Google Analytics for WinRT - Home を使いました。

Windows Runtime Component のプロジェクトをソリューションに追加する

↓ 詳しい方法は下記のドキュメントを見てください。

Walkthrough: Creating a simple component in C# or Visual Basic and calling it from JavaScript
チュートリアル : C# または Visual Basic での単純なコンポーネントの作成および JavaScript による呼び出し

  1. Solution Explorer でソリューションを右クリックして、Add > New Project... と選択します。
  2. Visual C# > Windows Runtime Component と選択して、プロジェクト名を適当につけて [OK] を押します。
  3. Solution Explorer でWindows Store アプリケーションのプロジェクトを右クリックして、Add Reference... を選択します。
  4. Solution > Projects と選択して、先ほどの Windows Runtime Component として作ったプロジェクトにチェックをつけて [OK] を押します。

↑ ここまででこんな感じになります。

GoogleAnalytics のライブラリをプロジェクトに追加する

先ほどの Windows Runtime Component のプロジェクトから利用できるように GoogleAnalytics のライブラリを追加します。

  1. Solution Explorer でプロジェクトを右クリックして、Add > New Folder と選択します。
  2. そのフォルダに 'Nascent.GoogleAnalytics.dll' をコピーします。
  3. Solution Explorer で Windows Runtime Component のプロジェクトを右クリックして、Add Reference... を選択します。
  4. [Browse...] ボタンを押して 'Nascent.GoogleAnalytics.dll' を選択します。
  5. 'Nascent.GoogleAnalytics.dll' にチェックをつけて [OK] を押します。

ライブラリの呼び出しをブリッジするクラスを書く

↓ こんな感じで。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Nascent.GoogleAnalytics;

namespace WindowsRuntimeGoogleAnalytics
{
    public sealed class Tracker
    {
        public string webPropertyId { get; set; }

        public Tracker(string id)
        {
            webPropertyId = id;
        }

        public void SetCustomVariable(int index, string name, string value)
        {
            AnalyticsTracker tracker = AnalyticsTracker.GetInstance(webPropertyId);
            tracker.SetCustomVariable(index, name, value);
        }

        public void TrackPageView(string page)
        {
            AnalyticsTracker tracker = AnalyticsTracker.GetInstance(webPropertyId);
            tracker.TrackPageView(page);
        }

        public void TrackEvent(string category, string action)
        {
            AnalyticsTracker tracker = AnalyticsTracker.GetInstance(webPropertyId);
            tracker.TrackEvent(category, action, null, null);
        }

        public void TrackEvent(string category, string action, string label)
        {
            AnalyticsTracker tracker = AnalyticsTracker.GetInstance(webPropertyId);
            tracker.TrackEvent(category, action, label, null);
        }
    }
}

JSのインターフェースを書く

↓ 直接呼んでもいいけどまとまってると何かと使いやすいのでこんな感じで。

(function () {
    "use strict";

    var Tracker = WinJS.Class.define(
        function (webPropertyId) {
            this.webPropertyId = webPropertyId;
            this.tracker = new WindowsRuntimeGoogleAnalytics.Tracker(webPropertyId);
        },

        {
            setCustomVariable: function (index, name, value) {
                var that = this;
                that.tracker.setCustomVariable(index, name, value);
            },

            trackPageView: function (page) {
                var that = this;
                that.tracker.trackPageView(page);
            },

            trackCurrentPageView: function () {
                var that = this;
                var page = Application.navigator.pageControl.uri.replace("ms-appx://" + Windows.ApplicationModel.Package.current.id.name.toLowerCase(), "");
                that.trackPageView(page);
            },

            trackEvent: function (category, action, label) {
                var that = this;
                that.tracker.trackEvent(category, action, label);
            }
        });

    WinJS.Namespace.define("GoogleAnalytics", {
        Tracker: new Tracker("UA-4291014-7")
    });
})();

WinJS から呼び出してみる

↓ たとえば、画面が表示されるたびにアクセスを記録するならこんな感じで。

ready: function (element, options) {
    GoogleAnalytics.Tracker.trackCurrentPageView();

    var listView = element.querySelector(".groupeditemslist").winControl;
    listView.groupHeaderTemplate = element.querySelector(".headertemplate");
〜(略)〜


↓ 今回のサンプルコードはこちらです。
kishikawakatsumi/GoogleAnalytics-for-WinJS · GitHub

リンクするだけで iOS 6 で Google Map が使えるようになる YAMapKit を公開しました。

kishikawakatsumi/YAMapKit · GitHub

YAMapKit は MapKit.framework と(ほぼ)互換性のある代替ライブラリです。
Google Maps Javascript API と UIWebView を利用して iOS 6 で Apple の標準地図の代わりに Google Map を使った表示ができます。
MapKit.framework と(ほぼ)互換性があるのでリンク先を差し替えるだけで動作します(たいていの場合は)。

あまりヘビーな利用には向きませんが、アプリケーションの中でちょっと MapKit を使って地図を表示したりピンを挿したりしているという場合に効果的です。

まだ未サポートの機能がたくさんあるので、手伝ってくれる方や、バグレポート、要望を歓迎します。

使い方

  1. MapKit.framework のリンクを外します。
  2. libMapKit.a をリンクします。
  3. CoreLocation.framework をリンクします。

できないこと

  • ジオコーディング(代わりに 'CLGeocoder' を使ってください)
  • カスタムビューのオーバーレイ表示(組み込みのオーバーレイ (MKPolylineView, MKCircleView など) しか使えません)
  • アノテーションのドラッグ&ドロップ
  • アノテーションのコールアウトを表示したあとで更新する

(たぶん他にもいっぱいあります)

利用例


Objective-C でサブクラスのインスタンスから任意のスーパークラスのメソッドを呼ぶ

サブクラスのインスタンスからポリモーフィズムを無視して任意のスーパークラスのメソッドを呼びます。


↓ 下のように Shape クラスと Shape クラスを継承した Path クラス、および Path クラス を継承した Circle があります。
それぞれのクラスで draw メソッドをオーバーライドしています。

////////////////////////////////////////////////////////////////////////
#pragma mark - Shape
////////////////////////////////////////////////////////////////////////
@interface Shape : NSObject
@end

@implementation Shape

- (void)draw {
    NSLog(@"%@", @"Shape.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Path
////////////////////////////////////////////////////////////////////////
@interface Path : Shape
@end

@implementation Path

- (void)draw {
    NSLog(@"%@", @"Path.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Circle
////////////////////////////////////////////////////////////////////////
@interface Circle : Path
@end

@implementation Circle

- (void)draw {
    NSLog(@"%@", @"Circle.");
}

@end


↓ Circle クラスのインスタンスから draw メソッドを呼び出すと Circle クラスの draw メソッドが実行されて "Circle." と出力されます。
たまに多態性を無視してスーパークラスや、スーパークラスのさらにスーパークラスのメソッドを実行したいということってありますよね。
そういうときは 対象メソッドの IMP (メソッドを参照する関数へのポインタ) を使います。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    Shape *shape = [[Circle alloc] init];
    [shape draw]; // => Circle.
    
    SEL selector = @selector(draw);
    
    void(*pathFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Path instanceMethodForSelector:selector];
    pathFunction(shape, selector); // => Path.
    
    void(*shapeFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Shape instanceMethodForSelector:selector];
    shapeFunction(shape, selector); // => Shape.
}

@end

↑ IMP の定義のままだと ARC が戻り値を retain しようとするので、戻り値が void の関数ポインタにキャストしています。


↓ 上記のコードの出力は下記になります。

2012-10-23 13:15:20.632 Monomorphism[50033:c07] Circle.
2012-10-23 13:15:20.633 Monomorphism[50033:c07] Path.
2012-10-23 13:15:20.633 Monomorphism[50033:c07] Shape.


サブクラスのインスタンスからポリモーフィズムを無視して任意のスーパークラスのメソッドが呼べました。
↓ 試したコードの全体を載せておきます。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Shape
////////////////////////////////////////////////////////////////////////
@interface Shape : NSObject
@end

@implementation Shape

- (void)draw {
    NSLog(@"%@", @"Shape.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Path
////////////////////////////////////////////////////////////////////////
@interface Path : Shape
@end

@implementation Path

- (void)draw {
    NSLog(@"%@", @"Path.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Circle
////////////////////////////////////////////////////////////////////////
@interface Circle : Path
@end

@implementation Circle

- (void)draw {
    NSLog(@"%@", @"Circle.");
}

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    Shape *shape = [[Circle alloc] init];
    [shape draw]; // => Circle.
    
    SEL selector = @selector(draw);
    
    void(*pathFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Path instanceMethodForSelector:selector];
    pathFunction(shape, selector); // => Path.
    
    void(*shapeFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Shape instanceMethodForSelector:selector];
    shapeFunction(shape, selector); // => Shape.
}

@end

iOS 6.0 と iOS 5.x の両方で動作するアプリケーションをビルドする設定

iOS 4.0 と iPhone OS 3.x の両方で動作するアプリケーションをビルドする設定 - 24/7 twenty-four seven

↑ こちらも参考に
iOS 4.0 が登場したくらいのときに上の記事を書いて、仕組みは変わってないのですけど Xcode 4.x 系だと UI が変わってるので現在のやり方をまとめます。

ベース SDK と Deployment Target を設定する

プロジェクトの "Build Settings" で "Base SDK" を "Latest iOS" にします。
前にも書きましたが、ベース SDK は最新を指定したほうがいいです。


プロジェクトの "Info" で "Deployment Target" をサポートする OS の最も低いバージョンにします。
(下の場合は iOS 5.0 以降で動作する。)


今なら、Base SDK 6.0 でビルドして、Deployment Target を 5.0 または 5.1 にするのが効果的でしょうか。2世代サポートということで。

新しい Framework を Weak Link (Optional) に設定する

古い環境には含まれていない Framework をリンクしていると、Dynamic Linker がシンボルのロードに失敗してアプリケーションが起動しません。
その場合は、新しい OS にのみ存在する Framework を Weak Link に設定します(今回は Social.framework)。

↑ リンクの設定はプロジェクトではなくターゲットに対してしか行えないので、ターゲットを選択して "Build Phases" > "Link Binary With Libraries" から設定します。
リンクするフレームワークを設定して、 Required > Optional に変更します。

OS のバージョンごとに処理を分岐する

古い API には存在しないセレクタを呼び出したり、クラスを参照したりするとクラッシュしますので必要に応じて処理を分岐します。

- (void)tweet:(id)sender
{
    Class clazz = NSClassFromString(@"SLComposeViewController");
    if (clazz) {
        SLComposeViewController *controller = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
        [self presentViewController:controller animated:YES completion:nil];
    } else {
        TWTweetComposeViewController *controller = [[TWLandscapeTweetComposeViewController alloc] init];
        [self presentViewController:controller animated:YES completion:nil];
    }
}

Auto Layout に注意

Auto Layout は iOS 6 以降しか使えません。Auto Layout を使用していると iOS 5.x などで起動時に関連のクラスが無くてクラッシュします。StoryBoard を新しく追加したときなどにうっかり Auto Layout のチェックをつけたままにしてしまったりするので注意しましょう。

iOS 6 では Supported interface orientations の順番に注意!

最近の Xcode ではアプリケーションが対応しているデバイスの向きをターゲットの Summary 画面から GUI を用いて設定できるようになりましたが、ここから設定する場合はボタンを押す順番に注意する必要があります。

というのも、この画面で設定した内容は、Info.plist の Supported interface orientations (UISupportedInterfaceOrientations) に反映されるのですが、この項目は Array の値で順番が起動時の状態に影響するからなのです。


上記の画面の状態になるように、ボタンを左から順に押していった場合、Info.plist の UISupportedInterfaceOrientations は下記のようになります。これは新規プロジェクトを作成した場合のデフォルト値です。


今度は同じ状態になるように、ボタンを「右から」順に押していきます。すると Info.plist は下記のようになります。
値の順番が変わっています。


実はこの順番は起動時の画面の向きに関係していて、iOS 6 では一番先頭に指定されている画面の向きで起動することになります。
つまり、前の例では縦画面で起動するのですが、後の例では横向きで起動することになります。


別のキーに Initial interface orientation (UIInterfaceOrientation) というものがあって、こちらを指定すると初期状態を指定できそうですが、試したところ iOS 6 ではどうもこの値は無視されるようです。

iOS 6 ではグループスタイルのテーブルビューの背景色がこっそり非推奨になっている。


iOS 6 では上記のカラーを生成するメソッドがヘッダのコメントでひそかに deprecated になっています。

UIInterface.h

// Group style table view backgrounds can no longer be represented by a simple color.
// If you want to have a background in your own view that looks like the table view background,
// then you should create an empty table view and place it behind your content.
+ (UIColor *)groupTableViewBackgroundColor; // This method will be deprecated during the 6.0 seed program


ドキュメントの記載は変わってないのですが、実際に使ってみると今までは下記のコードでピンストライプのカラーが設定されていましたが、iOS 6 だと真っ黒になってしまいます。

self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];


この背景色を使いたい場合は空のテーブルビューを設定しろということなので、iOS 6 から代替のコードは下記のようになります。

UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
[self.view addSubview:tableView];

はやりのシンボルフォントを iOS で画像として扱える SymbolFontKit を公開しました。

kishikawakatsumi/SymbolFontKit · GitHub

ScreenShot1

↑ シミュレータに表示されている画像やツールバーのボタン、タブバーのアイコンは全てフォントです。

シンボルフォントとは要するにアイコン画像などをフォント形式にしたものです。
Webだと最近のブラウザだとWebフォントが使えるので、利用者の環境にフォントがインストールされていなくても使えるので、最近は解像度非依存ということもあっていろいろな Github や Twitter などいろいろなサイトで利用されています。

シンボルフォントについて詳しくは下記のリンク先などを見てください。
Ligature Symbols 〜ほんとにべんりなフォントのはなし〜
【完全版】Ligature Symbols フォントセットの自作方法 - くらげだらけ
Ligature Symbols

で、フォントなのでベクターデータのためどんな解像度でもキレイに表示されることや、1つのアイコンで色違いや別のサイズを表示することが簡単だったり、はやっているので様々なデザインのシンボルフォントが使いやすいライセンスで入手できるなど、iOS でも利用できたら便利だと思って作りました。

使い方

  1. SFKImage.h/m をプロジェクトにコピーします。
  2. 利用したいフォントファイルをプロジェクトにコピーします。
  3. 上記でコピーしたフォントファイルのファイル名を UIAppFonts をキーにして Info.plist に追加します。

API は UIImage 互換なので UIImage と同様の使い方ができます。(実は現在は imageNamed: 以外のインスタンス化はできません)

SFKImage *image = [SFKImage imageNamed:@"print"];


インスタンス化した SFKImage オブジェクトは UIImage のインスタンスと同様に UIButton や UIImageView、UITabBarItem などに直接設定することができます。

self.imageView1.image = [SFKImage imageNamed:@"share"];

UITabBarItem *calendarTabBarItem = [[UITabBarItem alloc] initWithTitle:@"calendar" image:[SFKImage imageNamed:@"calender"] tag:1];
UITabBarItem *globeTabBarItem = [[UITabBarItem alloc] initWithTitle:@"globe" image:[SFKImage imageNamed:@"globe"] tag:2];
_tabBar.items = @[calendarTabBarItem, globeTabBarItem];


フォントのレンダリングは実際に画面に描画されるときまで遅延されるので、UIImage と違って、1つのインスタンスを途中で色や大きさを変えたりできます。

SFKImage *image = [SFKImage imageNamed:@"compass"];
image.size = CGSizeMake(20, 20);
image.color = [UIColor redColor];
self.imageView6.image = image;
    
image.size = CGSizeMake(40, 40);
image.color = [UIColor yellowColor];
self.imageView7.image = image;
    
image.size = CGSizeMake(80, 80);
image.color = [UIColor blueColor];
self.imageView8.image = image;


UIImage として振る舞うためにちょっと無茶をしているのでそのまま AppStore の審査に通るかどうかは「?」ですが近いうちに適当なアプリを提出して調査したいと思います。


SymbolFontKit は第1回iphone_dev_jp東京 iPhone/Mac Hackathon 〜みんなが幸せになるハッカソン〜の成果物です。

Objective-C や Xcode の新しい機能のほとんどは iOS4 以前でも動く

Objective-C Feature Availability Index に一覧表が載ってるのですが、ARCのWeak Reference 以外は iOS 4 以上、ほとんどはすべての iOS バージョンで動くので、古い環境を気にせずにジャンジャン使ってしまってOKです。

新しいリテラルや Subscripting は断然コードが書きやすく読みやすくなるのでこれはうれしいですね。


Objective-C Feature Availability Index

FeatureTools versionsOS X deploymentiOS deployment
Automatic Reference Counting (ARC)Xcode 4.2
(LLVM Compiler 3.0)
Requires modern runtime
Deploys back to OS X v10.7
Deploys back to iOS 5
Automatic Reference Counting without zeroing weak reference (“ARCLite”)Xcode 4.2
(LLVM Compiler 3.0)
Requires modern runtime
Deploys back to OS X v10.6
Deploys back to iOS 4
Default synthesis of @property instance variables and accessor methodsXcode 4.4 (LLVM Compiler 4.0)Requires modern runtime
Deploys back to OS X v10.6
Deploys back to iOS 4
Instance variables in class extensionsXcode 4.2
(LLVM Compiler 3.0)
Requires modern runtimeAll iOS releases
Instance variables in @implementation blockXcode 4.2
(LLVM Compiler 3.0)
Requires modern runtimeAll iOS releases
No forward method prototypes needed in @implementation blockXcode 4.3
(LLVM Compiler 3.1)
All releasesAll iOS releases
NSNumber, NSDictionary and NSArray literalsXcode 4.4
(LLVM Compiler 4.0)
All releasesAll iOS releases
@YES and @NO literalsXcode 4.4 and OS X 10.8 or later SDK
Xcode 4.5 and iOS 6 or later SDK
(LLVM Compiler 4.0)
All releasesAll iOS releases
NSDictionary and NSArray subscriptingXcode 4.4 and OS X 10.8 or later SDK
Xcode 4.5 and iOS 6 or later SDK
(LLVM Compiler 4.0)
Requires modern runtime
Deploys back to OS X v10.6
Deploys back to iOS 4

はてなブックマークの ShareKit 拡張を書きました

ShareKit/Classes/ShareKit/Sharers/Services/Hatena at hatena · kishikawakatsumi/ShareKit · GitHub


ShareKit といういろいろな外部サービスとの連携機能を提供するライブラリがあるのですが、それのプラグイン (ShareKit では Sharer と呼びます) として「はてなブックマーク」とリンクを共有するものを書きました。
オリジナルを fork して hatena ブランチにコミットしています。利用するには clone して hatena ブランチに切り替えます。


本家にPull Request を送ったので、もしかしたらマージされるかもしれません。
(既存の Sharer に日本のサービスはなかったのでイマイチ勝手がわかりませんでした。)


↓ 発端は fladdict さんとの下記のやりとりです。


はてなブックマークへの投稿は昔書いたことがあったので、認証を OAuth にしたらあとはカンタンそうだなと思って安請け合いしました。いやまあ、特に難しいことはなかったんですけど。




↑ ちょっとだけハマったのは、付属している OAuth ライブラリ (OAuthConsumer) が不具合のあるリビジョンのもので、シグネチャを組み立てるときに追加の OAuth パラメータの部分を2重に URL エンコードしていました。
そのため、はてなの OAuth は必ずコールバック URL を oauth_callback パラメータで指定する必要があるのですが、シグネチャが invalid になって OAuth が成功しないということがありました。


↓ こちらの不具合は別の Pull Request でパッチを送ったので現在は修正されています。
Fix double URI encode for extra OAuth parameters. by kishikawakatsumi · Pull Request #515 · ShareKit/ShareKit · GitHub



↑ そんなこんなでとりあえず書いてみた割りにはちゃんと使えてるようでよかったです。
OAuth で許可するスコープに write_private を書いたら失敗する点とか気になるところはあるけど。。。



↑ Pull Request しました。↓
Add Hatena Bookmark Sharer. by kishikawakatsumi · Pull Request #521 · ShareKit/ShareKit · GitHub

第1回iphone_dev_jp東京 iPhone/Mac Hackathon 〜みんなが幸せになるハッカソン〜 を開催しました

iphone_dev_jp東京 iPhone/Mac Hackathon : ATND
iPhone_dev_jpで、みんなが幸せになるハッカソンを開催します | fladdict


前回の勉強会で深津さんが「一発もののアプリじゃなくてきちんと使われるライブラリをドキュメント込みで作るハッカソンやったらいいんじゃない?」って話をしていたので、それはすばらしいと思ったのでやってみました。


ハッカソンってやったことなくて、勝手がわからずにかなりギリギリの告知になってしまったのですが、約30名の猛者が集まってくださいました。

とはいえ、私は1日でそんなに書けるものだろうかと不安だったので実はそれまでの1週間である程度メドを立てておこうとか思ってたのですが、意外と時間がなくてぶっつけになってしまいました。
でもなんとかなるもので、やっぱり集中して書いたほうがダンゼン効率がいいんだなあとか今さらながら思いました。


こういう周辺ライブラリは必要だなあと思っててもけっこう普段の時間には書こうと思ってもなかなか筆が進まなかったりするもので、こういう集まりは小規模でもいいので定期的にやっていこうというところを確認できた一日でした。
かなりメリットのあることがわかったので近いうちにまたやると思いますのでまたみなさん集まってくれたらうれしいです。

第3回iphone_dev_jp 東京iPhone/Mac勉強会を開催しました

第3回iphone_dev_jp 東京iPhone/Mac勉強会 : ATND

発表内容と資料のまとめ

http://mtl.recruit.co.jp/blog/2012/07/721_iphone_dev_jp_iphonemacmtl.html
会場を提供してくださったMTLフナミさんのすばらしいまとめです。
すべての資料と発表ごとに分けていただいたUstreamの動画を見ることができます。


↓ Slidrsで共有した資料
第3回 iphone_dev_jp 東京iPhone/Mac勉強会 - Slidrs

ネット配信の録画

↓ ニコニコ動画(プレミアム会員のひとは1週間はタイムシフト視聴できます)
7/21 iPhoneDev-jp 勉強会放送 - 2012/07/21 13:00開始 - ニコニコ生放送

↓ Youtube
http://www.youtube.com/playlist?list=PL286E9699885DAEA8&feature=mh_lolz

CoreData の 属性に現在時刻など固定値以外のデフォルト値を設定する

CoreData のオブジェクトに作成日や更新時刻が自動的に入ったらいいなと思うことってありますよね。固定の値ならばモデルエディタの Defaut の欄に設定しておけば初期値として自動的に設定されますが実行時の現在時刻のように変わる値はモデルエディタでは設定することができません。

このような値をデフォルト値として設定するには NSManagedObject の awakeFromInsert メソッドを使用します。

#import "RecentSearch+CoreData.h"

@implementation RecentSearch(CoreData)

- (void)awakeFromInsert {
    [super awakeFromInsert];
    self.createdAt = [NSDate date];
    self.updatedAt = self.createdAt;
}

↑ このように awakeFromInsert メソッドをオーバライドしておくと、このオブジェクトが新しく作成されたときに自動的に現在時刻が設定されます。


↓ ちなみに、これを書いている途中に発見したのですが、モデルエディタの Default のところに "now" や "today" と入力すると現在時刻が「固定値として」設定されます。


あくまでここで設定できる初期値は固定値なのでイマイチ何の役にたつのか分かりませんが、もしかしたら便利なのかもしれません。

今年のチケット争奪戦が終わったので WWDC チェッカーのソースコードを公開します


kishikawakatsumi/WWDCChecker-Mac · GitHub
kishikawakatsumi/WWDCChecker-iPhone · GitHub


WWDC 2012 がようやく発表されました。
チケットは2時間ほどで売り切れてしまいましたが、なんとか買うことができました。

これまでの傾向から激しい争奪戦になることは分かっていたので WWDC のサイト (WWDC - Apple Developer) を監視して、更新があったら手元の iPhone にプッシュ通知で知らせてくれるアプリケーションを作りました。
今回、無事に役目を果たすことができたので少々の解説をしつつ、来年のためにソースコードを公開します。


↓ ぞくぞくと寄せられる喜びの声



構成は、通知を受けるための iPhone アプリケーションと、常時起動していてサイトを監視し変更があったら通知する Mac アプリケーション の2つのソフトウェアからなります。
↓ iPhone アプリはこんな感じです。

といってもこの画像ではわかりにくいですが、WWDC のサイトに自動的に接続するようにしてあるブラウザです。
別にプッシュ通知を受けるために必要なだけなので特に機能は必要ないのですが、プッシュ通知から起動したあと、すぐに購入することができるようにこうしてみました。

↓ 通知が届いたときの様子


プッシュ通知には Parse.com を使いました。
1,000,000 通知/月までは無料で使えるので今回のような Ad Hoc で限られた人にだけ配布するような用途にはピッタリです。
また Parse は iOS 用には専用の SDK が提供されているので決まったコードを少し書くだけでプッシュ通知が受けられるようになります。


↓ これだけのコードで通知を受けられるようになります。キモはプッシュ通知なのでインストールして通知が有効になってさえいれば、アプリケーションは起動しててもしてなくてもどうでもいいので、これだけでほぼ完成といえます。

@implementation WWDCAppDelegate

@synthesize window;
@synthesize viewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#if !(TARGET_IPHONE_SIMULATOR)
    TESTFLIGHT_TAKEOFF;
#endif
    
    [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert];
    
    [Parse setApplicationId:@"SCRdFC3GKlr9WD5ElC9IGpBZewLxDhrl2zSQOKlu" 
                  clientKey:@"5cRNScoCLbMlnPbgwznv40TxNRJKrVQGmXWwbCH5"];
    
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    
    self.viewController = [[WWDCViewController alloc] initWithNibName:@"WWDCViewController" bundle:nil];
    window.rootViewController = viewController;
    [window makeKeyAndVisible];
    
    return YES;
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)newDeviceToken 
{
    [PFPush storeDeviceToken:newDeviceToken];
    [PFPush subscribeToChannelInBackground:@""];
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error 
{
    if ([error code] == 3010) {
        NSLog(@"%@", @"Push notifications don't work in the simulator!");
    }
    
    NSString *message = error.localizedDescription;
    
    NSBundle *bundle = [NSBundle mainBundle];
    NSDictionary *infoDictionary = [bundle localizedInfoDictionary];
    NSString *appName = [[infoDictionary count] ? infoDictionary : [bundle infoDictionary] objectForKey:@"CFBundleName"];
    
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:appName message:message delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
    [alert show];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 
{
    [PFPush handlePush:userInfo];
}

@end


Mac アプリケーションはいろいろ試しましたが、結局 WebView を使って一定の間隔でサイトにアクセスしてタイトルに変更がないかを比較する、という方式で監視することにしました。
HTTP リクエストを使ってダウンロードしたデータを比較したり、もっと厳密にやろうかと思ったのですがいろいろあってこの方法に落ち着きました。
これを家でつけっぱなしにしている Mac にインストールして準備完了です。


↓ Mac アプリのコードの抜粋です。Mac の WebView は自由にデータにアクセスできるから楽チンですね。Parse は SDK によるアクセスの他に REST API も用意されていて、プッシュ通知も REST API を使って iOS 以外から送信することができます。

- (void)onTimer:(NSTimer *)t
{
    if (!timer) {
        self.timer = [NSTimer timerWithTimeInterval:120.0 target:self selector:@selector(onTimer:) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    }
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://developer.apple.com/wwdc/"]];
    [request setHTTPShouldHandleCookies:NO];
    [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
    
    [[webView mainFrame] loadRequest:request];
}

- (void)webView:(WebView *)sender didReceiveTitle:(NSString *)title forFrame:(WebFrame *)frame 
{
    if ([title isEqualToString:@"Apple Worldwide Developers Conference 2011"]) {
        [self log:@"WWDC Checked... no changes\n"];
    } else {
        [self notify];
    }
}

- (void)notify {
    [self log:@"WWDC 2012 may have been announced!\n"];
    
    NSString *JSON = @"{\"channel\":\"\", \"data\":{\"alert\":\"WWDC 2012 may have been announced!\", \"sound\":\"notify.wav\"}}";
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://api.parse.com/1/push"]];
    [request setHTTPMethod:@"POST"];
    [request addValue:@"ICFdOE4GOlu8WN5KlC0ILpASiqIxShrl6zWKQKlu" forHTTPHeaderField:@"X-Parse-Application-Id"];
    [request addValue:@"ork5uoWbcoYxV8wXz34WLhNfKzmyAGEKTOG4JDKF" forHTTPHeaderField:@"X-Parse-REST-API-Key"];
    [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPBody:[JSON dataUsingEncoding:NSUTF8StringEncoding]];
    
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
     {
         if (error) {
             [self log:error.localizedDescription];
         }
     }];
}


Mac アプリのほうも、最初はウインドウなど何もなかったのですが、ちゃんと動いているのか分からないと心配で監視アプリの死活を監視するためのアプリが必要になって……とキリがないのでウインドウにログ出力をするようにしました。
これで変更があったり、アプリが動かなくなっていることが一目でわかるようになりました。
↓ 以下は発表があった当時の様子です。2分間隔なので誤差はありますがこの記録によると 21:34:32 に発表があったことになります。

売り切れたのがだいたい 23 時半ですので今年は2時間もたなかったということになりますね。

というわけで来年はもはや発表前に売り切れるんじゃないかという話もある WWDC ですが、少しでも有利に争奪戦に参加するためにぜひ有効に活用してみてください。
(上記のコード、および GitHub のコードは Parse.com や TestFlight のトークンを書き換えてあります。試される際は自分のアカウントのトークンに直してご利用ください。)