24/7 twenty-four seven

iOS/OS X application programing topics.

チュートリアルなどでUIPageControlを使うときは標準のアクションに対応するのを忘れずに



UIPageControlはiPhoneのホーム画面でも使われている、今何ページ目かを示すUIControlのサブクラスです。
最初のiOSからあって、特徴的なUIなのでフリックでページをめくる画面ではこれを使って現在のページを示すのが定番になっています。
特に最近では初回起動時のチュートリアル画面でよく使われます。


ただ、意外と経験のあるひとが書いたものでも、このコンポーネントがタップによって値が変わるコントロールであることを忘れているのをけっこう見ます。

これを忘れると、UIPageControlのドットのところをタップすると、ドットの場所は変わるのに画面は変わらないので、ちょっとマヌケな感じになってしまいます。


UIPageControlはUISliderなどと同様にUIControlのサブクラスなので、基本的にユーザーの操作によって値が変わるコントロールです。
見た目に特徴があるので、つい装飾のためだけのコンポーネントだと思ってしまいますが、タップによってページを切り替えることを想定されています。


↓ 下記はアップルが提供しているUIPageControlの使い方を示すサンプルコードです。
UISliderなどと同様に、タップで値が変わったときはUIControlEventValueChangedのイベントが発生するので、そのイベントに対応したアクションで処理をします(このサンプルコードでは`changePage:`)。


PageControl

- (void)gotoPage:(BOOL)animated
{
    NSInteger page = self.pageControl.currentPage;
 
    // load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
    [self loadScrollViewWithPage:page - 1];
    [self loadScrollViewWithPage:page];
    [self loadScrollViewWithPage:page + 1];
    
    // update the scroll view to the appropriate page
    CGRect bounds = self.scrollView.bounds;
    bounds.origin.x = CGRectGetWidth(bounds) * page;
    bounds.origin.y = 0;
    [self.scrollView scrollRectToVisible:bounds animated:animated];
}
 
- (IBAction)changePage:(id)sender
{
    [self gotoPage:YES];    // YES = animate
}


ただ、実際はUIPageControlのドットのところがタップできることを知らないひとも多いので、チュートリアルなどで単に装飾のために使ってるのであれば、`pageControl.userInteractionEnabled = NO;`などとして、タップに反応しないようにしてしまうのも手かと思います。

CoreTextを使って簡単に画像付きリッチテキストを表示できるSECoreTextViewに編集機能がつきました。

kishikawakatsumi/SECoreTextView · GitHub
iOS/Macの両方で使えて、文字の選択やリンクのクリックに対応したテキストビューをテスト公開しました。 - 24/7 twenty-four seven



前に書いたSECoreTextViewに編集機能を実装しました (iOSのみ)。


SECoreTextViewはCoreTextを使って簡単にクリッカブルなリンクや画像付きのリッチテキストを表示できるテキストビューの代替実装としてのライブラリです。


以前のものはそこそこ簡単に豊かな表現ができるのでこれはこれでけっこう実用的だったと思います。
↓ このように画像を含めたテキストを表示したり、リンクはクリックに反応して任意の処理をすることができます。
画像に限らず、画面に表示できるものはボタンでもその他のビューでもブロックを渡して任意の描画をすることも可能です。


iOS ScreenShot 1 iOS ScreenShot 1


そんな感じで、表示のみなら標準のUITextViewやUIWebViewをがんばって使うよりは柔軟で取り回しやすいのでけっこう便利に使っていたのですが、だんだん表示だけでは物足りなくなってきたので編集できるようにしてみました。

iOS ScreenShot 1 20130927032539

これまでの実装に加えて、UITextInput/UIKeyInput Protocol を実装して、標準のテキストビューと同じようにキーボードや日本語変換システムの入力を処理しています。


たいていの画面に表示できるものは扱えるので、わりと万能なテキスト編集コンポーネントになったんじゃないかなとおもいます。
だいたいの動きがわかるムービーを作ったのでこちらもどうぞ。
SECoreTextView Demo on Vimeo


UITextInputの実装やCoreTextはいろいろおもしろかったので、技術的なところはまた機会をみて書こうと思います。
今後はパフォーマンス・チューニングやOS Xのほうの実装などを予定しています。あ、それと音声入力への対応ですね。


メッセージアプリやブログエディタなどに、応用がきいて使いやすいと思いますので、いろいろ実戦投入していただけるとうれしいです。
今のうちなら何かあったらけっこうすぐに私が対応できると思います。よろしくお願いします。

複雑な正規表現を分かりやすくするライブラリ VerbalExpressions の Objective-Cバージョンを書きました

https://github.com/VerbalExpressions/ObjectiveCVerbalExpressions
↑ 本家にマージされました。

https://github.com/kishikawakatsumi/ObjectiveCVerbalExpressions

概要

VerbalExpressions はメソッドチェーンとわかりやすい名前を使って、正規表現を読みやすくしようという試みです。
↓ オリジナルはJavaScriptのライブラリのようです。
https://github.com/VerbalExpressions/JSVerbalExpressions


iOS Dev Weeklyの106号でObjective-Cの移植はまだ無いみたいに書いてあったので、やってみました。
(実際は2つほど先に書かれたものがありました)

↓ 私が書いた Objective-C 版のライブラリを使うと下記のように記述できます。

// Create an example of how to test for correctly formed URLs
VerbalExpressions *verEx = [VerbalExpressions instantiate:^(VerbalExpressions *ve) {
    ve.startOfLine(YES)
    .then(@"http")
    .maybe(@"s")
    .then(@"://")
    .maybe(@"www")
    .anythingBut(@" ")
    .endOfLine(YES);
}];

最初のインスタンス化は Blocks 付きのメソッドを使わずに普通にインスタンス化することもできます。
(alloc] init] や new を使ってもいいでしょう)

VerbalExpressions *verEx = [VerbalExpressions expressions];
verEx.startOfLine(YES).then(@"http").maybe(@"s").then(@"://").maybe(@"www").anythingBut(@" ").endOfLine(YES);

仕組みについて

通常 Objective-C とこういったメソッドチェーンを多用する、いわゆる「流れるようなインターフェース (fluent interface)」はあまり相性がよくありません。

例えば、別の人の書かれた Objective-C 版の VerbalExpressions ですが、普通にメソッドチェーンを使って書くと下記のようになります。
https://github.com/sakiwei/ObjectiveCVerbalExpressions

// url matches
VerbalExpressions *tester = [[[[[[[VerEX() startOfLine] then:@"http"] maybe:@"s"] then:@"://"] maybe:@"www."] anythingBut:@" "] endOfLine];

↑ 読みやすくないこともないですが、書きやすくはないですよね。
普通の Objective-C のメソッド呼び出しは両側にカッコを追加して行かなければならないので、チェーンを追加しようとすると最初に戻って開きカッコを追加したりしないといけないのでこのやり方だと、考えながら書くっていうのが難しいです。


なので今回は Blocks を利用して他のライブラリと同様にドットでチェーンできるようにしてみました。

VerbalExpressions *verEx = [VerbalExpressions expressions];
verEx.startOfLine(YES).then(@"http").maybe(@"s").then(@"://").maybe(@"www").anythingBut(@" ").endOfLine(YES);


方法としては VerbalExpressions クラスに自分自身を戻り値として返すブロックをプロパティとして定義しています。

@interface VerbalExpressions : NSObject

@property (nonatomic, readonly) VerbalExpressions *(^startOfLine)(BOOL enable);
@property (nonatomic, readonly) VerbalExpressions *(^endOfLine)(BOOL enable);
@property (nonatomic, readonly) VerbalExpressions *(^find)(NSString *value);
@property (nonatomic, readonly) VerbalExpressions *(^then)(NSString *value);
@property (nonatomic, readonly) VerbalExpressions *(^maybe)(NSString *value);
@property (nonatomic, readonly) VerbalExpressions *(^anything)();
@property (nonatomic, readonly) VerbalExpressions *(^anythingBut)(NSString *value);


プロパティに実装は下記のようになっていて、プロパティを参照するとブロックが実行されて正規表現が組み立てられるというしくみです。そしてこのブロックは自分自身のインスタンスを返すので、その戻り値に対してドットでチェーンできる、というように書かれています。

- (VerbalExpressions *(^)(NSString *))maybe
{
    return ^VerbalExpressions *(NSString *value) {
        value = [self sanitize:value];
        self.add([NSString stringWithFormat:@"(%@)?", value]);
        return self;
    };
}


この方法の課題としてはオーバーロードができないので、例えばオリジナルの JS ライブラリでは `startOfLine()` と `startOfLine(bool)` という 2 つのメソッドがあるのですが、Blocks のプロパティだとどちらも同じ名前になってしまうので、引数付きのものだけ用意されています。


実際に有用かどうかは使いどころによると思いますが、おもしろい試みだと思いますので、ぜひ使ってみてください。
バグレポートや Pull Request もお待ちしています。

OS X 10.8.4でXcodeでiOS Simulatorを実行したときにSIGABRTでアプリがクラッシュすることがある問題の暫定的な対処(修正済み)

先日のアップデートでOS Xを10.8.4にしてから、XcodeからアプリケーションをiOS Simulatorで実行したときにSIGABRTでアプリが起動せずにクラッシュすることが多くなってしまいました。

私の環境だとアプリを終了させてから再度実行、という手順だと数回に1回、実行中に⌘+Rで再起動という方法だと100%クラッシュしてしまいました。


Developer Forumsの情報からデバッガをLLDBからGDBに変更すると発生しないことを確認しました。


もしくはいったん終了 (⌘+.) させてから再度実行 (⌘+R) する場合は数回に1回くらいの頻度だったので、少し面倒ですがそれでもいいかもしれません。

Xcode 4.6.3 で修正されました。

UITextView でタップ可能なリンクをカスタマイズする

UITextView では dataDetectorTypes を設定することでデータタイプに応じて自動的にクリック可能なリンクとして表示してくれます。

例えば下記のようにしていすると、URLが含まれていた場合、タップ可能なリンクとして表示されます。

cell. tweetTextView. dataDetectorTypes = UIDataDetectorTypeLink;



他にも次のようなデータタイプが用意されていて、電話番号、住所、イベント(日付や「今週」「今夜」など)っぽい文字列をリンクにすることができます。

typedef NS_OPTIONS(NSUInteger, UIDataDetectorTypes) {
    UIDataDetectorTypePhoneNumber   = 1 << 0,          // Phone number detection
    UIDataDetectorTypeLink          = 1 << 1,          // URL detection    
#if __IPHONE_4_0 <= __IPHONE_OS_VERSION_MAX_ALLOWED
    UIDataDetectorTypeAddress       = 1 << 2,          // Street address detection
    UIDataDetectorTypeCalendarEvent = 1 << 3,          // Event detection
#endif    

    UIDataDetectorTypeNone          = 0,               // No detection at all
    UIDataDetectorTypeAll           = NSUIntegerMax    // All types
};

ただ、任意の文字列をリンクにすることができなかったり、リンクをタップした時の処理があらかじめ決まったものに固定されている(Safariを開く、電話をかける、カレンダーに予定を登録する、など)など、実際に使ううえではかなり制限があります。

今回、こちらのリッチテキストビューライブラリ SECoreTextView を書くにあたって、たまたま使えそうなテクニックをいくつか発見したので紹介します。

ただし、それらのテクニックを使っても標準の UITextView で頑張れる限界はけっこうすぐ来るので、凝ったことをする場合には前述のライブラリなど別の手段で解決するのがいいと思います。

任意の文字列をタップ可能なリンクにする (iOS 6〜)

iOS 6 から UIKit のコンポーネントのいろいろなテキストに NSAttributedString が使えるようになりました。
UITextView にも attributedText というプロパティが追加され、スタイルを指定できるようになったのでそれを使います。


実は OS X には 10.0 の頃から NSLinkAttributeName という属性があります。
読んで字のごとく、文字列にリンク属性を付加します。


ただ NSLinkAttributeName はなぜか iOS には用意されていません。


しかし、たまたま発見したのですが NSLinkAttributeName 定数が宣言されていないだけで、NSLinkAttributeName が表す文字列を直接指定してみると iOS でも機能することがわかりました。


NSLinkAttributeName は文字列定数で @"NSLink" と定義されています。
そこで、次のように @"NSLink" + リンク文字列という形で属性を指定します。
↓ 下記の例は Twitter のツイートに含まれる @screen_name とハッシュタグをリンク属性として指定しています。

NSArray *textEentities = [TwitterText entitiesInText:text];
for (TwitterTextEntity *textEentity in textEentities) {
    if (textEentity.type == TwitterTextEntityScreenName) {
        NSString *screenName = [text substringWithRange:textEentity.range];
        [attributedString addAttributes:@{@"NSLink": screenName}
                                  range:textEentity.range];
    } else if (textEentity.type == TwitterTextEntityHashtag) {
        NSString *hashTag = [text substringWithRange:textEentity.range];
        [attributedString addAttributes:@{@"NSLink": hashTag}
                                  range:textEentity.range];
    }
}

cell.tweetTextView.attributedText = [[SETwitterHelper sharedInstance] attributedStringWithTweet:tweet];


↓ 実行結果は次のようになります。URLに加えてメンションやハッシュタグがリンクになっているのがわかるでしょうか。
各リンクはすべてタップ可能です。


ただし、これらのリンクのうち、@"NSLink" を指定して作ったリンクについてはタップしても Safari を開いたりはしてくれません。(開く URL も無いので当然ですが)
かろうじて長押しすると次のようなアクションシートが表示されて文字列のコピーができる、というような動作をします。
(Open は何も起こらない)


これでは実用にできませんので次にリンクの処理をカスタマイズする方法を紹介します。

リンクをタップしたときに任意の処理を実行する

Data Detector による自動リンク化ではあらかじめ決まった処理がシステムによって実行されるということは前に述べました。
また、@"NSLink" によるリンク化もそれだけでは実用的ではないことがわかりました。


そこで、既定の処理をフックすることでタップ時に任意の処理を実行するという方法を紹介します。

実は自動リンク化された URL のリンクをタップしたときは UIApplication の openURL: メソッドが呼ばれます。
つまり openURL: メソッドをオーバーライドすれば、そこで任意の処理を実行することができます。


次のように UIApplication のサブクラス Application を作成し、openURL: メソッドをオーバーライドします。
とりあえず、URLをログ出力するように変更します。

@interface Application : UIApplication

@end

@implementation Application

- (BOOL)openURL:(NSURL *)url
{
    NSLog(@"%@", url.absoluteString);
    return NO;
}

@end


アプリケーションクラスは UIApplicationMain 関数で指定されているので main.m を次のように変更します。

int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, @"Application", NSStringFromClass([AppDelegate class]));
    }
}


これでアプリケーションクラスは UIApplication ではなく、Application が使われるようになりました。


ここまでで URL のリンクをタップしたときは カスタマイズしたメソッドが呼ばれるようになるのですが、実は @"NSLink" でリンク化したメンションやハッシュタグのリンクは openURL: メソッドが呼ばれません。


というのも @"NSLink" で単に文字列を指定した場合は自動的に applewebdata://[UUID]/[リンク文字列] のような URL としてリンク化されるので、applewebdata は開けるスキーマではないため、openURL: メソッドが呼ばれないのです。


ただし、ここまでわかっていれば問題は難しくありません。
要は開けるような URL にしてしまえばよいのです。


ということで @"NSLink" に対する値をちょっと変更して、適当なスキーマを付けてしまいます。
openURL: が呼ばれるもので、他にリンク文字列として使用されないものがいいでしょう。
ここではメンションに ftp を、ハッシュタグに maps を指定します。

NSArray *textEentities = [TwitterText entitiesInText:text];
for (TwitterTextEntity *textEentity in textEentities) {
    if (textEentity.type == TwitterTextEntityScreenName) {
        NSString *screenName = [text substringWithRange:textEentity.range];
        [attributedString addAttributes:@{@"NSLink": [NSString stringWithFormat:@"ftp:%@", screenName]}
                                  range:textEentity.range];
    } else if (textEentity.type == TwitterTextEntityHashtag) {
        NSString *hashTag = [text substringWithRange:textEentity.range];
        [attributedString addAttributes:@{@"NSLink": [NSString stringWithFormat:@"maps:%@", hashTag]}
                                  range:textEentity.range];
    }
}


これで、 メンションやハッシュタグのリンクをタップしたときも openURL: が呼ばれるようになり、スキーマによって何がタップされたのかも区別できるようになりました。

- (BOOL)openURL:(NSURL *)url
{
    NSString *scheme = url.scheme;
    if ([scheme hasPrefix:@"http"]) {
        // 通常のリンクの処理
        NSLog(@"%@", url.absoluteString);
    } else if ([scheme isEqualToString:@"ftp"]) {
        // メンションの処理
        NSLog(@"%@", url.absoluteString);
    } else if ([scheme hasPrefix:@"maps"]) {
        // ハッシュタグの処理
        NSLog(@"%@", url.absoluteString);
    }
    return NO;
}

リンクの書式を変更する (iOS 6〜)

せっかくいろいろなリンクを作れるようになったのですから、標準の青い文字色とアンダーラインでは物足りないですよね。
NSAttributedString はそもそもリッチテキストを表現するためのオブジェクトなので NSAttributedString でスタイルを指定することによってリンクの書式をカスタマイズすることができます。


メンションのリンクを赤色に、ハッシュタグをグレーの太字に変えてみます。

NSArray *textEentities = [TwitterText entitiesInText:text];
for (TwitterTextEntity *textEentity in textEentities) {
    if (textEentity.type == TwitterTextEntityScreenName) {
        NSString *screenName = [text substringWithRange:textEentity.range];
        [attributedString addAttributes:@{
         @"NSLink": [NSString stringWithFormat:@"ftp:%@", screenName],
         NSForegroundColorAttributeName: [UIColor redColor]}
                                  range:textEentity.range];
    } else if (textEentity.type == TwitterTextEntityHashtag) {
        NSString *hashTag = [text substringWithRange:textEentity.range];
        [attributedString addAttributes:@{
         @"NSLink": [NSString stringWithFormat:@"maps:%@", hashTag],
         NSForegroundColorAttributeName: [UIColor grayColor],
                    NSFontAttributeName: [UIFont boldSystemFontOfSize:14.0f]}
                                  range:textEentity.range];
    }
}


↓ 実行結果は下のようになります。メンションとハッシュタグの書式が変わっているのがわかるでしょうか。
もちろんタップ可能なのは変わりません。


残念ながら、アンダーラインを消すことはできないようです。NSUnderlineStyleAttributeName をゼロに指定してみたのですが、効果はありませんでした。


それではここまでのコードを共有しておきます。
kishikawakatsumi/TextViewLinks · GitHub

解決が難しい問題

ただ、ここまでがんばっても UITextView を使う以上解決が難しい問題が残ります。
例えば、今回のテーブルビューセルに使うような場合だと、テキストビューの置いてあるところはテキストビューにタッチが取られてセルの選択ができない(テキストビューの userInteractionEnabled を NO にするとセルの選択はできるようになるが、今度はリンクがタップできなくなる)ことや、セルの選択をしたときに文字がハイライト色に変わらない(highlightedAttributedText のようなプロパティがあれば…)、などがあります。


これらの問題についてもがんばれば何とかなりそうですが、それをやろうとすると、そろそろコストが釣り合わないかなという気がします。
なのでそれ以上凝ったことをしたり見た目にこだわるのであれば、下記のようなサードパーティのライブラリの使用を検討してみたらいいのではないかと思います。


SECoreTextView は Mac/iOS の両方で簡単にリッチテキストやクリック可能なリンクを扱うことのできるライブラリです。
さらに任意の画像やビューを文字列と同様に取扱うこともできますので UIWebView のライトウェイトな代替コンポーネントとしても使用することができます。

kishikawakatsumi/SECoreTextView · GitHub

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

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


しばらく休んでいたのですが久しぶりにいつもおなじみのVOYAGE GROUPさんの会場をお借りして開催いたしました。


今回は幸運なことにEvernote本社からMac版EvernoteのUIのリニューアルの指揮をされましたJack Hirschさんにきていただき、背景や開発手法などをお話していただきました。

大勢のかたにきていただき、質問が飛び交う活発な会することができてよかったと思います。

Jack Hirschさんおよび、Evernoteのかたがたも参加者の熱意に感銘を受けたと言っていただけました。


通常の発表においても、興味深い内容の話ばかりで、参加していただいたかたには満足いただけたのではないでしょうか。


ホストしていただいたVOYAGE GROUPの @lesamoureuses さん、@huin さんはじめ、手伝っていただいたかた、発表してくださったかた、参加してくださったかた、どうもありがとうございました。


今度はまたハッカソンやりたいね、という話を聞いたので次はハッカソンかなーと思っています。
来月のiOS 7の発表次第でUIコンポーネントはいろいろ必要なものが出てくると思うので、iOS7時代のUIコンポーネントを書く、みたいなのがいいかなと思っています。


また、今回発表してくださった id:ninjinkun の所属する株式会社はてなでは毎年恒例のサマーインターンの募集が始まっています。


はてなのインターンシッププログラムは非常にしっかりした内容でレベルが高いことで有名で、はてなのインターンに来た学生が最終的に他の企業からひっぱりだこになってしまって困っているみたいな話もよく聞きます。

id:ninjinkun によると今回はサーバーサイドだけでなくiPhoneアプリケーションのカリキュラムもあるということなので、両方をバランスよく経験できると貴重な機会だと思います。

参加資格のある学生のかたはぜひ応募してみると良いと思います。

株式会社はてな

発表内容と資料のまとめ

EasyStyleGKさん Objective-C atomicity
_ishkawaさん iOS5で動くUIRefreshControlの作り方
cocoponさん フラットデザインの話
novi_さん クライアント系iOSアプリのつくりかた(実装と開発プロセスの話)
Jack Hirschさん Evernote のユーザエクスペリエンスアプローチ
ninjinkunさん NJKWebViewProgressについて
kishikawa katsumi Mac/iOSのリッチテキストの表示について


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

UIImageView で 'Aspect Fit (UIViewContentModeScaleAspectFit)' を指定したときの画像サイズを取得する

↓ 例えばこんなふうに UIImageView に 'Aspect Fit' を指定して表示させたときの領域を知りたいことってありますよね。


がんばって計算してもいいのですが、AVFoundation.framework の次の関数で簡単に取得できます。

CGRect AVMakeRectWithAspectRatioInsideRect(CGSize aspectRatio, CGRect boundingRect);

ドキュメントによると、ムービーを CALayer に表示するときに領域にフィットさせるのに便利ということですが、画像に使っても便利です。


上記の画像だと UIImageView の大きさは 280x508 で、画像のサイズは 2047x1199 です。
そこで下記のコードを実行すると返ってくる CGRect は {{0, 171.997}, {280, 164.006}} でピッタリ画像の領域に一致します。

CGRect frame = AVMakeRectWithAspectRatioInsideRect(image.size, self.imageView.bounds);
UIView *overlayView = [[UIView alloc] initWithFrame:frame];
overlayView.layer.borderColor = [[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:0.5f] CGColor];
overlayView.layer.borderWidth = 4.0f;

[self.imageView addSubview:overlayView];


別の画像 (533x800) で実行した場合は {{0, 43.8687}, {280, 420.263}} になりました。

iPhone/iPadで画像をクロッピングするライブラリを公開しました

kishikawakatsumi/PEPhotoCropEditor · GitHub

PEPhotoCropEditor は iPhone/iPad アプリに画像をクロッピングする機能を簡単に追加します。
UIは標準のPhotos.appに似ていておもしろい動きをします。

ScreenShot 1 [Movie 1

インストール

CocoaPodsでインストールできます。

pod 'PEPhotoCropEditor'

または、Lib/ ディレクトリと Resources/ ディレクトリのファイルをすべてプロジェクトにコピーして、下記のフレームワークをリンクしてください。

  • QuartzCore.framework
  • AVFoundation.framework

使い方

ビューコントローラを使う場合

(UINavigationController を使わなくても動きます。)

PECropViewController *controller = [[PECropViewController alloc] init];
controller.delegate = self;
controller.image = self.imageView.image;

UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:controller];
[self presentViewController:navigationController animated:YES completion:NULL];
ビューを直接使う場合
self.cropView = [[PECropView alloc] initWithFrame:contentView.bounds];
[self.view addSubview:self.cropView];
Cropping された画像を取り出す
デリゲートメソッドから
- (void)cropViewController:(PECropViewController *)controller didFinishCroppingImage:(UIImage *)croppedImage
{
    [controller dismissViewControllerAnimated:YES completion:NULL];
    self.imageView.image = croppedImage;
}
ビューのプロパティから
UIImage *croppedImage = self.cropView.croppedImage;

iOS/Macの両方で使えて、文字の選択やリンクのクリックに対応したテキストビューをテスト公開しました。

kishikawakatsumi/SECoreTextView · GitHub

iOS ScreenShot 1


OS X ScreenShot 1


SECoreTextView はリッチテキストの表示と文字の選択(現在はOS Xのみ)やリンクがクリック可能だったりするテキストビューです。
別のアプリでテーブルビューのセルにリンクを含むテキストを表示するのに、既存のものでMacで使えるいい感じのものが今ひとつ見つからなかったので書きました。

OS X で使うだけだとなんなので、せっかくだから iOS にも対応してみました。

UITableVIewやNSTableVIewのセルで使うと便利だと思います。

iOS のほうは半日くらいでちょちょっと書いただけなのでおかしなところが結構あると思うので見つけたら教えてください。

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

リンクするだけで 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];