24/7 twenty-four seven

iOS/OS X application programing topics.

iOSアプリケーションでキーボードショートカットに対応する

↓ より丁寧な記事はこちらで公開しています。


iOS 7のSafariやメールでは外部キーボードを使用した際に利用できるできるショートカットが以前より充実したことが話題になりました。


iOS 7ではキーボードショートカットを実装するためのAPIが追加されているので、サードパーティのアプリケーションもキーボードショートカットに対応することができます。


特定のキーボードショートカットに応答するには下記のプロパティを実装します。

@property(nonatomic, readonly) NSArray *keyCommands


このプロパティは`UIResponder`クラスに`readonly`として宣言されているので、`UIView`のサブクラスか、`UIViewController`のサブクラスでgetterメソッドをオーバーライドするのがオーソドックスなやり方になります。
(ビューコントローラはデフォルトでFirst Responderになれないので、ビューコントローラに実装する場合は`canBecomeFirstResponder`で`YES`を返すのを忘れないようにしましょう。)


ここで返す値は`UIKeyCommand`クラスのインスタンスの配列です。
`UIKeyCommand`クラスはキーの入力の組み合わせを表すクラスでこちらもiOS 7から新たに導入されました。
`UIKeyCommand`のオブジェクトは専用のクラスメソッドを使用して作成します。

+ (UIKeyCommand *)keyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)modifierFlags action:(SEL)action;


例えば「コマンドキー+S」というショートカットに対応するには下記のようにします。

- (NSArray *)keyCommands
{
    return @[[UIKeyCommand keyCommandWithInput:@"s"
                                 modifierFlags:UIKeyModifierCommand
                                        action:@selector(handleCommand:)]];
}


`input`パラメータはキー押した時に入力される文字を指定します。
`modifierFlags`はコマンド(⌘)キーやオプション(⌥)キー、シフトキーなどの修飾キーの組み合わせをビットマスクで指定します。
例えば「コントロールキー+オプションキー+S」というショートカットを表す`UIKeyCommand`のインスタンスは下記になります。

- (NSArray *)keyCommands
{
    return @[[UIKeyCommand keyCommandWithInput:@"s"
                                 modifierFlags:UIKeyModifierControl | UIKeyModifierAlternate
                                        action:@selector(handleCommand:)]];
}


ここで注意すべきなのは`UIKeyModifierControl | UIKeyModifierAlternate`という組み合わせで表現されるのは「コントロールキーとオプションキーを両方押す」という組み合わせであって、コントロールキーまたはオプションキーをどちらか片方だけ押した場合は別のショートカットキーと扱われる点です。


`modifierFlags`を指定しなかった場合は単独のキー入力に対応することができます。
またエスケープシーケンスを利用して`@"\r"`や`@"\t"`をキー入力として指定するとEnterキーやタブキーの入力に応答することができます。


キー入力のうち矢印キーとエスケープキーの特別なキーについては専用の定数が用意されています。

UIKIT_EXTERN NSString *const UIKeyInputUpArrow         NS_AVAILABLE_IOS(7_0);
UIKIT_EXTERN NSString *const UIKeyInputDownArrow       NS_AVAILABLE_IOS(7_0);
UIKIT_EXTERN NSString *const UIKeyInputLeftArrow       NS_AVAILABLE_IOS(7_0);
UIKIT_EXTERN NSString *const UIKeyInputRightArrow      NS_AVAILABLE_IOS(7_0);
UIKIT_EXTERN NSString *const UIKeyInputEscape          NS_AVAILABLE_IOS(7_0);


これらを組み合わせると、例えば矢印キーでテーブルビューを選択したり、エスケープキーでキャンセル、タブキーで移動など一般のビジネスアプリケーションでよくあるキー操作を実装することができます。


下記はサンプルコードとしてほぼすべてのキー入力と修飾キーの組み合わせに反応して、押されたキーの組み合わせを画面に表示するプログラムです。参考にしてください。
https://github.com/kishikawakatsumi/KeyboardShortcuts


20131117181446 20131117181446

20131117181446 20131117181446

iOS 7のエンタープライズ向け新機能 Guided Access(アクセスガイド)のカスタマイズ

20131108031636 20131108031636

アクセスガイドとは

iOS 6から導入された機能にアクセスガイドがあります。
これは一時的にホームボタンを無効にして1つのアプリケーションしか使えないようにしたり(クラッシュしても自動的にそのアプリケーションが再起動する)、タッチ操作を部分的に無効にして触ってほしくないボタンなどを使えなくするなど、いわゆるキオスクモードを実現できる機能です。

展覧会や店舗などでナビゲーション端末として一般のひとに貸し出して使ってもらう場合などに利用されます。


iOS 7では上記の機能に加え、アプリケーションごとに制限を定義できるようになりました。

これによって、これまでの機能でできる制限だと、アプリケーションの作りによってはボタンの位置などでうまく制限がかけられなかったりすることがありましたが、その問題がアプリケーションの対応によって解決されます。
特に企業向けのアプリケーションを提供しているところにとっては魅力的な改善だと思います。

iOS 7から追加されたアクセスガイドのカスタマイズ

アプリケーション側で対応するにはiOS 7から追加されたAPI`UIGuidedAccessRestrictionDelegate`を使います。
`UIGuidedAccessRestrictionDelegate`のメソッドを`UIApplicationDelegate`に実装すると必要に応じてシステムから呼び出されます。


`UIGuidedAccessRestrictionDelegate`の定義は次のようになります。

@required
- (NSArray *)guidedAccessRestrictionIdentifiers;
- (void)guidedAccessRestrictionWithIdentifier:(NSString *)restrictionIdentifier didChangeState:(UIGuidedAccessRestrictionState)newRestrictionState;
- (NSString *)textForGuidedAccessRestrictionWithIdentifier:(NSString *)restrictionIdentifier;

@optional
- (NSString *)detailTextForGuidedAccessRestrictionWithIdentifier:(NSString *)restrictionIdentifier;

@end


このうち、`detailTextForGuidedAccessRestrictionWithIdentifier:`以外のメソッドはすべて実装する必要があります。

`guidedAccessRestrictionIdentifiers`ではアプリケーションで独自に定義した制限を示す識別子を文字列の配列で返します。
それぞれが区別できればどのようなものでも構いません。

例えば、これまでの例と同様に、設定画面へのアクセス、記事のシェア、および削除を禁止したいとします。
それぞれに適当な識別子を決めて下記のようなコードになります。

- (NSArray *)guidedAccessRestrictionIdentifiers
{
    return @[@"allow-settings", @"allow-share", @"allow-delete"];
}


次にそれぞれの制限に対応する簡潔な説明を定義します。

- (NSString *)textForGuidedAccessRestrictionWithIdentifier:(NSString *)restrictionIdentifier
{
    if ([restrictionIdentifier isEqualToString:@"allow-settings"]) {
        return NSLocalizedString(@"Access Settings", nil);
    } else if ([restrictionIdentifier isEqualToString:@"allow-share"]) {
        return NSLocalizedString(@"Allow Share", nil);
    } else if ([restrictionIdentifier isEqualToString:@"allow-delete"]) {
        return NSLocalizedString(@"Allow Delete", nil);
    }
    
    return nil;
}


必要なら詳しい説明を追加します。

- (NSString *)detailTextForGuidedAccessRestrictionWithIdentifier:(NSString *)restrictionIdentifier
{
    if ([restrictionIdentifier isEqualToString:@"allow-settings"]) {
        return NSLocalizedString(@"Allow change settings.", nil);
    } else if ([restrictionIdentifier isEqualToString:@"allow-share"]) {
        return NSLocalizedString(@"Allow share link to other services.", nil);
    } else if ([restrictionIdentifier isEqualToString:@"allow-share"]) {
        return NSLocalizedString(@"Allow delete entries.", nil);
    }
    
    return nil;
}


上記で定義した制限の有効・無効、あるいはアクセスガイド自体のオン・オフが切り替わったときには下記のデリゲートメソッドが呼ばれるので、そこでボタンを消したり、UIを更新したりします。
制限が有効になったのか無効になったのかの判断は`newRestrictionState`の値を使います。
アクセスガイド自体がオフになったときは制限を識別子についてこのメソッドがそれぞれ呼ばれて、`newRestrictionState`の値は`UIGuidedAccessRestrictionStateAllow`になっています。

- (void)guidedAccessRestrictionWithIdentifier:(NSString *)restrictionIdentifier didChangeState:(UIGuidedAccessRestrictionState)newRestrictionState
{
    if ([restrictionIdentifier isEqualToString:@"allow-settings"]) {
        if (newRestrictionState == UIGuidedAccessRestrictionStateAllow) {
            // Settingsボタンを表示する
        } else if (newRestrictionState == UIGuidedAccessRestrictionStateDeny) {
            // Settingsボタンを消す
        }
    } else if ([restrictionIdentifier isEqualToString:@"allow-share"]) {
        if (newRestrictionState == UIGuidedAccessRestrictionStateAllow) {
            // シェアボタンを表示する
        } else if (newRestrictionState == UIGuidedAccessRestrictionStateDeny) {
            // シェアボタンを消す
        }
    } else if ([restrictionIdentifier isEqualToString:@"allow-delete"]) {
        if (newRestrictionState == UIGuidedAccessRestrictionStateAllow) {
            // 削除機能を有効にする
        } else if (newRestrictionState == UIGuidedAccessRestrictionStateDeny) {
            // 削除機能を無効にする
        }
    }
}


また、いつでも`UIGuidedAccessRestrictionStateForIdentifier()`という関数(iOS 7から)を使って任意の制限が有効かどうかを調べることができます。

if (UIGuidedAccessRestrictionStateForIdentifier(@"allow-settings") == UIGuidedAccessRestrictionStateDeny) {
    // 設定画面へのアクセスが無効になっている場合の処理
}


アクセスガイド自体が有効か無効かは`UIAccessibilityIsGuidedAccessEnabled()`という関数でわかりますし、アクセスガイドの状態が変わったときに`UIAccessibilityGuidedAccessStatusDidChangeNotification`の通知を受けることもできます。(どちらもiOS 6から)。


ここまで記述した状態でアクセスガイドの画面を表示して「オプション」で表示される項目が次のようになります。

20131108031636


赤で示した部分が先ほど追加したアプリケーション独自の制限になります。
スイッチがオンの場合は許可、オフの場合は制限をかけるという意味になります。

上記の状態では記事の共有と削除を禁止し、設定画面へのアクセスは許可するということになります。
この状態でアクセスガイドを再開した場合、状態変化のデリゲートメソッドが呼ばれるので制限の変更に応じて適宜UIの更新や対応する処理をします。


以上です。いかがでしょうか。
特にビジネス用途で使われることを想定したアプリケーションでは強力に機能するのではないでしょうか。

親指シフトキーボードが使えるノートアプリ「N+Note」をリリースしました。


入力に親指シフト(NICOLA)配列のキーボードが使えるエディタアプリです。
iOSではシステムキーボードを置き換える`inputView`プロパティというAPIが提供されているので、それを利用しています。
なので、どこでも親指シフトのキーボードが使えるというわけではなく、このアプリを使う場合だけ、ということになります。


親指シフトは「同時シフト」という入力方法が一般的ということで、普通のシフトと違って、「同時に」押すという入力が主流のようです。なので、意外にキーボードのグラフィックを用意してキーを割り当てるだけではダメで、同時判定とかけっこう大変といえば大変でした。


実は私は親指シフトキーボードは使ったことはなくて、調べた以上のことはわからないのでFacebookの親指シフトユーザー会の方々にいろいろとサポートいただきました。なんとかリリースできる形になったのはそのおかげでありとても感謝しています。


作ってるときは勘違いしていたのですが、本格的に入力するにはBluetoothなどの外付けの親指シフトキーボードを使えばいいと思っていて、あくまでもそれと比べて外出先などで少し使う場合に慣れたもので打てればいい、くらいのニーズを満たすものがいいのかなと思ってたのですが、実はBluetoothの親指シフトキーボードは製品として存在しないということで、iOSデバイスで親指シフトが使える環境は他になかったとのことです。そういう話もあるので出せてよかったかなと思います。


現状はまだまだ機能も少ないのでこれから改善していきたいと考えています。
アプリ内に意見を投稿するフォームも用意していますので要望などいただけたらうれしいです。
よろしくお願いします。


20131107010430 N+Note for NICOLA - Katsumi Kishikawa

UUIDを少し短くするUUIDShortener

kishikawakatsumi/UUIDShortener · GitHub


ちょっと仕事でUUIDっぽい文字列を限られた幅の場所に表示する必要があったので書きました。

具体的にはレシートに識別子を印字したいという要件で、識別子はUUIDを振っているのでそれでいいのですが、レシートに印字するにはちょっと長すぎて2行になってしまうのでその点が問題でした。


そこでレシートの幅に収まるようにUUIDを別の表現に変換するのが`UUIDShortener`です。


実際に適用してみると下記のようになります。

20131101003300


上記のレシートに印字されている`NJ6NYLNKBRGSNCUF54Z53R4FVU`という文字列は、このライブラリを使って復元すると`6A7CDC2D-AA0C-4D26-8A85-EF33DDC785AD`というUUIDに復元されます。


UUIDは32桁の16進表記ですが、`UUIDShortener`を使って圧縮すると26桁のアルファベットと数字表記になります。
区切り記号も合わせると10桁ぶん短く表示できます。


使いかたは下記のようになります。

UUIDを圧縮する
/* Shorten UUID string */
NSUUID *UUID = [NSUUID UUID]; // => 40256F2F-3211-49CD-BC1F-DD5197D2F0F9
NSString *shortUUIDString = UUID.shortUUIDString;
NSLog(@"Short UUID:\t%@", shortUUIDString);
// => Short UUID:	    IASW6LZSCFE43PA73VIZPUXQ7E
圧縮した文字列から元のUUIDを復元する
/* Restore UUID string from short string */
NSString *restoredString = [NSUUID UUIDStringFromShortUUIDString:shortUUIDString];
NSLog(@"Restored UUID:\t%@", restoredString);
// => Restored UUID:	40256F2F-3211-49CD-BC1F-DD5197D2F0F9


仕組みですが、UUIDをいったんバイナリに戻し、それをBase32でエンコードしています。
もともと16進表記だったものが使える文字が増えるので結果的に短く表現できるということになります。


また、Base32はO(オー)、l(エル)と紛らわしい0(ゼロ)と1(イチ)があらかじめ除かれているので、検索などで識別子を入力する場合や、問題を調査するときに口頭で伝えてもらう場合などに都合がいいというのも良い点でした。大文字小文字を区別する必要がないというのも使い勝手がいいエンコード方式です。

参考

Base32 - Wikipedia, the free encyclopedia


もっと短くしたいという場合にはBase64Ascii85 (Base85)などを使えばいいかと思います。ただ、大文字小文字の区別ができたり、記号が入ったりするので入力や伝達が少し難しくなりますね。

エンジニア募集中

ユビレジでは現在エンジニアをiOS、サーバーサイドともに積極的に募集中です。
興味のあるかたは私までお声がけください。

追加ダウンロードフォントを含むiOS 7で使えるフォント一覧

出力の方法

iOS 7からは`kCTFontDownloadableAttribute`という属性が追加されているのでそれを利用します。
この方法で数えると、すべてのフォント数は283になりました。
iOS 7で新たに使えるようになった日本語のフォントとしてはOsakaとOsaka-等幅フォントがあります(追加ダウンロードフォント)。
かつてのMacの日本語システムフォントです。懐かしいですね。

+ (void)downloadableFonts
{
    NSDictionary *attributes = @{(id)kCTFontDownloadableAttribute : (id)kCFBooleanTrue};
    CTFontDescriptorRef fontDescriptor = CTFontDescriptorCreateWithAttributes((CFDictionaryRef)attributes);
    CFArrayRef matchedFontDescriptors = CTFontDescriptorCreateMatchingFontDescriptors(fontDescriptor, NULL);
    
    NSMutableDictionary *familyNames = [[NSMutableDictionary alloc] init];
    NSInteger numberOfFonts = 0;
    NSMutableString *text = [[NSMutableString alloc] init];
    for (UIFontDescriptor *fontDescriptor in (__bridge NSArray *)matchedFontDescriptors) {
        NSString *familyName = fontDescriptor.fontAttributes[UIFontDescriptorFamilyAttribute];
        NSString *displayName = fontDescriptor.fontAttributes[UIFontDescriptorVisibleNameAttribute];
        NSString *postscriptName = fontDescriptor.postscriptName;
        
        if (!familyNames[familyName]) {
            familyNames[familyName] = familyName;
            [text appendFormat:@"<b>%@</b>\n\n", familyName];
        }
        NSMutableDictionary *fontDict = [NSMutableDictionary dictionary];
        fontDict[@"displayName"] = displayName;
        fontDict[@"postscriptName"] = postscriptName;
        fontDict[@"descriptor"] = fontDescriptor;
        NSArray *languages = fontDescriptor.fontAttributes[@"NSCTFontDesignLanguagesAttribute"];
        fontDict[@"languages"] = [languages componentsJoinedByString:@", "];
        
        [text appendFormat:@"- %@ \"%@\" [%@]\n", postscriptName, displayName, [languages componentsJoinedByString:@", "]];
        
        numberOfFonts++;
    }
    
    NSLog(@"%@", text);
}

フォント一覧(日本語フォントは[ja])

AlFirat

  • Al-Firat "Al-Firat" [ar]

AlKhalil

  • Al-KhalilBold "Al-Khalil Bold" [ar]
  • Al-Khalil "Al-Khalil" [ar]

AlRafidain

  • Al-Rafidain "Al-Rafidain" [ar]

Al Bayan

  • AlBayan "Al Bayan Plain" [en]
  • AlBayan-Bold "Al Bayan Bold" [en]

Algiers

  • Algiers "Algiers" [ar]

AlRafidain AlFanni

  • AlRafidainAlFanni "Al-Rafidain Al-Fanni" [ar]

Al Tarikh

  • AlTarikh "Al Tarikh" [en]

Andale Mono

  • AndaleMono "Andale Mono" [en]

Apple Chancery

  • Apple-Chancery "Apple Chancery" [en]

Apple Braille

  • AppleBraille "Apple Braille" [en]
  • AppleBraille-Outline6Dot "Apple Braille Outline 6 Dot" [en]
  • AppleBraille-Outline8Dot "Apple Braille Outline 8 Dot" [en]
  • AppleBraille-Pinpoint6Dot "Apple Braille Pinpoint 6 Dot" [en]
  • AppleBraille-Pinpoint8Dot "Apple Braille Pinpoint 8 Dot" [en]

AppleGothic

  • AppleGothic "AppleGothic Regular" [ko]

AppleMyungjo

  • AppleMyungjo "AppleMyungjo Regular" [ko]

Apple SD Gothic Neo

  • AppleSDGothicNeo-Bold "Apple SD Gothic Neo Bold" [ko]
  • AppleSDGothicNeo-ExtraBold "Apple SD GothicNeo ExtraBold" [ko]
  • AppleSDGothicNeo-Heavy "Apple SD Gothic Neo Heavy" [ko]
  • AppleSDGothicNeo-Light "Apple SD Gothic Neo Light" [ko]
  • AppleSDGothicNeo-Medium "Apple SD Gothic Neo Medium" [ko]
  • AppleSDGothicNeo-Regular "Apple SD Gothic Neo Regular" [ko]
  • AppleSDGothicNeo-SemiBold "Apple SD Gothic Neo SemiBold" [ko]
  • AppleSDGothicNeo-Thin "Apple SD Gothic Neo Thin" [ko]
  • AppleSDGothicNeo-UltraLight "Apple SD Gothic Neo UltraLight" [ko]

Apple Symbols

  • AppleSymbols "Apple Symbols" [en]

Arial Black

  • Arial-Black "Arial Black" [en]

Arial

  • Arial-BoldItalicMT "Arial Bold Italic" [en]
  • Arial-BoldMT "Arial Bold" [en]
  • Arial-ItalicMT "Arial Italic" [en]
  • ArialMT "Arial" [en]

Arial Narrow

  • ArialNarrow "Arial Narrow" [en]
  • ArialNarrow-Bold "Arial Narrow Bold" [en]
  • ArialNarrow-BoldItalic "Arial Narrow Bold Italic" [en]
  • ArialNarrow-Italic "Arial Narrow Italic" [en]

Arial Unicode MS

  • ArialUnicodeMS "Arial Unicode MS" [en]

Ayuthaya

  • Ayuthaya "Ayuthaya" [en]

Baghdad

  • Baghdad "Baghdad" [en]

Bangla MN

  • BanglaMN "Bangla MN" [en]
  • BanglaMN-Bold "Bangla MN Bold" [en]

Basra

  • Basra-Bold "Basra Bold" [ar]
  • Basra "Basra" [ar]

Beirut

  • Beirut "Beirut" [en]

Big Caslon

  • BigCaslon-Medium "Big Caslon Medium" [en]

Book Antiqua

  • BookAntiqua "BookAntiqua" [en]
  • BookAntiqua-Italic "BookAntiqua-Italic" [en]
  • BookAntiqua-BoldItalic "BookAntiqua-BoldItalic" [en]
  • BookAntiqua-Bold "BookAntiqua-Bold" [en]

Bookman Old Style

  • BookmanOldStyle "Bookman Old Style" [en]
  • BookmanOldStyle-Italic "Bookman Old Style Italic" [en]
  • BookmanOldStyle-BoldItalic "Bookman Old Style Bold Italic" [en]
  • BookmanOldStyle-Bold "Bookman Old Style Bold" [en]

Brush Script MT

  • BrushScriptMT "Brush Script MT Italic" [en]

Chalkboard

  • Chalkboard "Chalkboard" [en]
  • Chalkboard-Bold "Chalkboard Bold" [en]

Comic Sans MS

  • ComicSansMS "Comic Sans MS" [en]
  • ComicSansMS-Bold "Comic Sans MS Bold" [en]

Corsiva Hebrew

  • CorsivaHebrew "Corsiva Hebrew" [en]
  • CorsivaHebrew-Bold "Corsiva Hebrew Bold" [en]

DecoType Naskh

  • DecoTypeNaskh "DecoType Naskh" [en]

Devanagari MT

  • DevanagariMT "Devanagari MT" [en]
  • DevanagariMT-Bold "Devanagari MT Bold" [en]

BiauKai

  • DFKaiShu-SB-Estd-BF "BiauKai" [zh-Hant]

Wawati SC

  • DFWaWaSC-W5 "Wawati SC Regular" [zh-Hans]

Wawati TC

  • DFWaWaTC-W5 "Wawati TC Regular" [zh-Hant]

Dijla

  • Dijla "Dijla" [ar]

Diwan Kufi

  • DiwanKufi "Diwan Kufi" [en]

Diwan Thuluth

  • DiwanThuluth "Diwan Thuluth" [en]

Farisi

  • Farisi "Farisi" [en]

Lantinghei SC

  • FZLTZHK--GBK1-0 "Lantinghei SC Demibold" [zh-Hans]
  • FZLTXHK--GBK1-0 "Lantinghei SC Extralight" [zh-Hans]
  • FZLTTHK--GBK1-0 "Lantinghei SC Heavy" [zh-Hans]

Lantinghei TC

  • FZLTZHB--B51-0 "Lantinghei TC Demibold" [zh-Hant]
  • FZLTXHB--B51-0 "Lantinghei TC Extralight" [zh-Hant]
  • FZLTTHB--B51-0 "Lantinghei TC Heavy" [zh-Hant]

Garamond

  • Garamond "Garamond " [en]
  • Garamond-Italic "Garamond Italic" [en]
  • Garamond-BoldItalic "Garamond Bold Italic" [en]
  • Garamond-Bold "Garamond Bold" [en]

Gujarati MT

  • GujaratiMT "Gujarati MT" [en]
  • GujaratiMT-Bold "Gujarati MT Bold" [en]

Gurmukhi MN

  • GurmukhiMN "Gurmukhi MN" [en]
  • GurmukhiMN-Bold "Gurmukhi MN Bold" [en]

Gurmukhi Sangam MN

  • GurmukhiSangamMN "Gurmukhi Sangam MN" [en]
  • GurmukhiSangamMN-Bold "Gurmukhi Sangam MN Bold" [en]

Hannotate SC

  • HannotateSC-W5 "Hannotate SC Regular" [zh-Hans]

Hannotate TC

  • HannotateTC-W5 "Hannotate TC Regular" [zh-Hant]
  • HannotateSC-W7 "Hannotate SC Bold" [zh-Hans]
  • HannotateTC-W7 "Hannotate TC Bold" [zh-Hant]

HanziPen SC

  • HanziPenSC-W3 "HanziPen SC Regular" [zh-Hans]

HanziPen TC

  • HanziPenTC-W3 "HanziPen TC Regular" [zh-Hant]
  • HanziPenSC-W5 "Weibei SC Bold" [zh-Hans]
  • HanziPenTC-W5 "HanziPen TC Bold" [zh-Hant]

Herculanum

  • Herculanum "Herculanum" [en]

Hiragino Sans GB

  • HiraginoSansGB-W3 "ヒラギノ角ゴ 簡体中文 W3" [zh-Hans]
  • HiraginoSansGB-W6 "ヒラギノ角ゴ 簡体中文 W6" [zh-Hans]

Hiragino Kaku Gothic Pro

  • HiraKakuPro-W3 "ヒラギノ角ゴ Pro W3" [ja]
  • HiraKakuPro-W6 "ヒラギノ角ゴ Pro W6" [ja]

Hiragino Kaku Gothic Std

  • HiraKakuStd-W8 "ヒラギノ角ゴ Std W8" [ja]

Hiragino Kaku Gothic StdN

  • HiraKakuStdN-W8 "ヒラギノ角ゴ StdN W8" [ja]

Hiragino Maru Gothic Pro

  • HiraMaruPro-W4 "ヒラギノ丸ゴ Pro W4" [ja]

Hiragino Maru Gothic ProN

  • HiraMaruProN-W4 "ヒラギノ丸ゴ ProN W4" [ja]

Hiragino Mincho Pro

  • HiraMinPro-W3 "ヒラギノ明朝 Pro W3" [ja]
  • HiraMinPro-W6 "ヒラギノ明朝 Pro W6" [ja]

YuMincho

  • YuMin-Medium "游明朝体 ミディアム" [ja]
  • YuMin-Demibold "游明朝体 デミボールド" [ja]

YuGothic

  • YuGo-Bold "游ゴシック体 ボールド" [ja]
  • YuGo-Medium "游ゴシック体 ミディアム" [ja]

Hoefler Text

  • HoeflerText-Ornaments "Hoefler Text Ornaments" [en]

Impact

  • Impact "Impact" [en]

InaiMathi

  • InaiMathi "InaiMathi" [en]

Iowan Old Style Black

  • IowanOldStyle-Black "Iowan Old Style Black" [en]
  • IowanOldStyle-BlackItalic "Iowan Old Style Black Italic" [en]

Iowan Old Style

  • IowanOldStyle-Bold "Iowan Old Style Bold" [en]
  • IowanOldStyle-BoldItalic "Iowan Old Style Bold Italic" [en]
  • IowanOldStyle-Italic "Iowan Old Style Italic" [en]
  • IowanOldStyle-Roman "Iowan Old Style Roman" [en]
  • IowanOldStyle-Titling "Iowan Old Style Titling" [en]

PilGi

  • JCfg "PilGi Regular" [ko]

HeadLineA

  • JCHEadA "HeadLineA Regular" [ko]

GungSeo

  • JCkg "GungSeo Regular" [en]

PCMyungjo

  • JCsmPC "PCMyungjo Regular" [ko]

Kannada MN

  • KannadaMN "Kannada MN" [en]
  • KannadaMN-Bold "Kannada MN Bold" [en]

Kefa

  • Kefa-Regular "Kefa Regular" [en]
  • Kefa-Bold "Kefa Bold" [en]

Khmer MN

  • KhmerMN "Khmer MN" [en]
  • KhmerMN-Bold "Khmer MN Bold" [en]

Khmer Sangam MN

  • KhmerSangamMN "Khmer Sangam MN" [en]

Kokonor

  • Kokonor "Kokonor Regular" [en]

Koufi Abjadi

  • KoufiAbjadi "Koufi Abjadi" [ar]

Krungthep

  • Krungthep "Krungthep" [en]

KufiStandardGK

  • KufiStandardGK "KufiStandardGK" [en]

Laimoon

  • Laimoon "Laimoon" [ar]

Lao MN

  • LaoMN "Lao MN" [en]
  • LaoMN-Bold "Lao MN Bold" [en]

Lao Sangam MN

  • LaoSangamMN "Lao Sangam MN" [en]

Apple LiGothic

  • LiGothicMed "Apple LiGothic Medium" [zh-Hant]

LiHei Pro

  • LiHeiPro "LiHei Pro" [zh-Hant]

LiSong Pro

  • LiSongPro "LiSong Pro" [zh-Hant]

Apple LiSung

  • LiSungLight "Apple LiSung Light" [zh-Hant]

Lucida Grande

  • LucidaGrande "Lucida Grande" [en]
  • LucidaGrande-Bold "Lucida Grande Bold" [en]

.Lucida Grande UI

  • .LucidaGrandeUI ".Lucida Grande UI" [en]
  • .LucidaGrandeUI-Bold ".Lucida Grande UI Bold" [en]

Malayalam MN

  • MalayalamMN "Malayalam MN" [en]
  • MalayalamMN-Bold "Malayalam MN Bold" [en]

Microsoft Sans Serif

  • MicrosoftSansSerif "Microsoft Sans Serif" [en]

Gurmukhi MT

  • MonotypeGurmukhi "Gurmukhi MT" [en]

Mshtakan

  • Mshtakan "Mshtakan" [en]
  • MshtakanBold "Mshtakan Bold" [en]
  • MshtakanBoldOblique "Mshtakan BoldOblique" [en]
  • MshtakanOblique "Mshtakan Oblique" [en]

Muna

  • Muna "Muna" [en]
  • MunaBold "Muna Bold" [en]
  • MunaBlack "Muna Black" [en]

Myanmar MN

  • MyanmarMN "Myanmar MN" [en]
  • MyanmarMN-Bold "Myanmar MN Bold" [en]

Myanmar Sangam MN

  • MyanmarSangamMN "Myanmar Sangam MN" [en]

Nadeem

  • Nadeem "Nadeem" [en]

Nanum Brush Script

  • NanumBrush "Nanum Brush Script" [ko]

Nanum Pen Script

  • NanumPen "Nanum Pen Script" [ko]

NanumGothic

  • NanumGothic "NanumGothic" [ko]
  • NanumGothicBold "NanumGothic Bold" [ko]
  • NanumGothicExtraBold "NanumGothic ExtraBold" [ko]

NanumMyeongjo

  • NanumMyeongjo "NanumMyeongjo" [ko]
  • NanumMyeongjoBold "NanumMyeongjoBold" [ko]
  • NanumMyeongjoExtraBold "NanumMyeongjoExtraBold" [ko]

New Peninim MT

  • NewPeninimMT "New Peninim MT" [en]
  • NewPeninimMT-Bold "New Peninim MT Bold" [en]
  • NewPeninimMT-BoldInclined "New Peninim MT Bold Inclined" [en]
  • NewPeninimMT-Inclined "New Peninim MT Inclined" [en]

Nisan

  • Nisan "Nisan" [ar]

Oriya MN

  • OriyaMN "Oriya MN" [en]
  • OriyaMN-Bold "Oriya MN Bold" [en]

Osaka

  • Osaka "Osaka" [ja]
  • Osaka-Mono "Osaka−等幅" [ja]

Plantagenet Cherokee

  • PlantagenetCherokee "Plantagenet Cherokee" [en]

PT Sans

  • PTSans-Regular "PT Sans" [en]
  • PTSans-Italic "PT Sans Italic" [en]
  • PTSans-Bold "PT Sans Bold" [en]
  • PTSans-BoldItalic "PT Sans Bold Italic" [en]

PT Sans Caption

  • PTSans-Caption "PT Sans Caption" [en]
  • PTSans-CaptionBold "PT Sans Caption Bold" [en]

PT Sans Narrow

  • PTSans-Narrow "PT Sans Narrow" [en]
  • PTSans-NarrowBold "PT Sans Narrow Bold" [en]

Raanana

  • Raanana "Raanana" [en]
  • RaananaBold "Raanana Bold" [en]

Raya

  • Raya "Raya" [ar]

Sana

  • Sana "Sana" [en]

Sathu

  • Sathu "Sathu" [en]

Savoye LET

  • SavoyeLetPlain "Savoye LET Plain:1.0" [en]

.Savoye LET CC.

  • .SavoyeLetPlainCC "Savoye LET Plain CC.:1.0" [en]

Hei

  • SIL-Hei-Med-Jian "Hei Regular" [zh-Hans]

Kai

  • SIL-Kai-Reg-Jian "Kai Regular" [zh-Hans]

Silom

  • Silom "Silom" [en]

Sinhala MN

  • SinhalaMN "Sinhala MN" [en]
  • SinhalaMN-Bold "Sinhala MN Bold" [en]

Somer

  • Somer "Somer" [ar]

Baoli SC

  • STBaoli-SC-Regular "Baoli SC Regular" [zh-Hans]

STFangsong

  • STFangsong "STFangsong" [zh-Hans]

STHeiti

  • STHeiti "STHeiti" [zh-Hans]

STIXGeneral

  • STIXGeneral-Bold "STIXGeneral-Bold" [en]
  • STIXGeneral-BoldItalic "STIXGeneral-BoldItalic" [en]
  • STIXGeneral-Italic "STIXGeneral-Italic" [en]
  • STIXGeneral-Regular "STIXGeneral-Regular" [en]

STIXIntegralsD

  • STIXIntegralsD-Bold "STIXIntegralsD-Bold" [en]
  • STIXIntegralsD-Regular "STIXIntegralsD-Regular" [en]

STIXIntegralsSm

  • STIXIntegralsSm-Bold "STIXIntegralsSm-Bold" [en]
  • STIXIntegralsSm-Regular "STIXIntegralsSm-Regular" [en]

STIXIntegralsUp

  • STIXIntegralsUp-Bold "STIXIntegralsUp-Bold" [en]
  • STIXIntegralsUp-Regular "STIXIntegralsUp-Regular" [en]

STIXIntegralsUpD

  • STIXIntegralsUpD-Bold "STIXIntegralsUpD-Bold" [en]
  • STIXIntegralsUpD-Regular "STIXIntegralsUpD-Regular" [en]

STIXIntegralsUpSm

  • STIXIntegralsUpSm-Bold "STIXIntegralsUpSm-Bold" [en]
  • STIXIntegralsUpSm-Regular "STIXIntegralsUpSm-Regular" [en]

STIXNonUnicode

  • STIXNonUnicode-Bold "STIXNonUnicode-Bold" [en]
  • STIXNonUnicode-BoldItalic "STIXNonUnicode-BoldItalic" [en]
  • STIXNonUnicode-Italic "STIXNonUnicode-Italic" [en]
  • STIXNonUnicode-Regular "STIXNonUnicode-Regular" [en]

STIXSizeFiveSym

  • STIXSizeFiveSym-Regular "STIXSizeFiveSym-Regular" [en]

STIXSizeFourSym

  • STIXSizeFourSym-Bold "STIXSizeFourSym-Bold" [en]
  • STIXSizeFourSym-Regular "STIXSizeFourSym-Regular" [en]

STIXSizeOneSym

  • STIXSizeOneSym-Bold "STIXSizeOneSym-Bold" [en]
  • STIXSizeOneSym-Regular "STIXSizeOneSym-Regular" [en]

STIXSizeThreeSym

  • STIXSizeThreeSym-Bold "STIXSizeThreeSym-Bold" [en]
  • STIXSizeThreeSym-Regular "STIXSizeThreeSym-Regular" [en]

STIXSizeTwoSym

  • STIXSizeTwoSym-Bold "STIXSizeTwoSym-Bold" [en]
  • STIXSizeTwoSym-Regular "STIXSizeTwoSym-Regular" [en]

STIXVariants

  • STIXVariants-Bold "STIXVariants-Bold" [en]
  • STIXVariants-Regular "STIXVariants-Regular" [en]

Kaiti SC

  • STKaiti-SC-Black "Kaiti SC Black" [zh-Hans]
  • STKaiti-SC-Bold "Kaiti SC Bold" [zh-Hans]

Kaiti TC

  • STKaiTi-TC-Bold "Kaiti TC Bold" [zh-Hant]
  • STKaiti-SC-Regular "Kaiti SC Regular" [zh-Hans]
  • STKaiTi-TC-Regular "Kaiti TC Regular" [zh-Hant]

Libian SC

  • STLibian-SC-Regular "Libian SC Regular" [zh-Hans]

Songti SC

  • STSongti-SC-Black "Songti SC Black" [zh-Hans]
  • STSongti-SC-Light "Songti SC Light" [zh-Hans]

Songti TC

  • STSongti-TC-Light "Songti SC Light" [zh-Hant]
  • STSongti-SC-Bold "Songti SC Bold" [zh-Hans]
  • STSongti-TC-Bold "Songti TC Bold" [zh-Hant]
  • STSongti-SC-Regular "Songti SC Regular" [zh-Hans]
  • STSongti-TC-Regular "Songti TC Regular" [zh-Hant]
  • STXihei "STXihei" [zh-Hans]

Xingkai SC

  • STXingkai-SC-Bold "Xingkai SC Bold" [zh-Hans]
  • STXingkai-SC-Light "Xingkai SC Light" [zh-Hans]

Yuanti SC

  • STYuanti-SC-Bold "Yuanti SC Bold" [zh-Hans]
  • STYuanti-SC-Light "Yuanti SC Light" [zh-Hans]
  • STYuanti-SC-Regular "Yuanti SC Regular" [zh-Hans]

Tahoma

  • Tahoma "Tahoma" [en]
  • Tahoma-Bold "Tahoma Bold" [en]

Tamil MN

  • TamilMN "Tamil MN" [en]
  • TamilMN-Bold "Tamil MN Bold" [en]

Telugu MN

  • TeluguMN "Telugu MN" [en]
  • TeluguMN-Bold "Telugu MN Bold" [en]

Waseem

  • Waseem "Waseem" [en]
  • WaseemLight "Waseem Light" [en]

Webdings

  • Webdings "Webdings" [en]

Weibei SC

  • Weibei-SC-Bold "Weibei SC Bold" [zh-Hans]

Weibei TC

  • Weibei-TC-Bold "Weibei TC Bold" [zh-Hant]

Wingdings

  • Wingdings-Regular "Wingdings" [en]

Wingdings 2

  • Wingdings2 "Wingdings 2" [en]

Wingdings 3

  • Wingdings3 "Wingdings 3" [en]

Yaziji

  • Yaziji "Yaziji" [ar]

Yuppy SC

  • YuppySC-Regular "Yuppy SC Regular" [zh-Hans]

Yuppy TC

  • YuppyTC-Regular "Yuppy TC Regular" [zh-Hant]

Zawra

  • Zawra-Bold "Zawra Bold" [ar]
  • Zawra-Heavy "Zawra Heavy" [ar]

Century Gothic

  • CenturyGothic "Century Gothic" [en]
  • CenturyGothic-Bold "Century Gothic Bold" [en]
  • CenturyGothic-Italic "Century Gothic Italic" [en]
  • CenturyGothic-BoldItalic "Century Gothic Bold Italic" [en]

Century Schoolbook

  • CenturySchoolbook "Century Schoolbook" [en]
  • CenturySchoolbook-Bold "Century Schoolbook Bold" [en]
  • CenturySchoolbook-Italic "Century Schoolbook Italic" [en]
  • CenturySchoolbook-BoldItalic "Century Schoolbook Bold Italic" [en]

Tw Cen MT

  • TwCenMT-Regular "Tw Cen MT" [en]
  • TwCenMT-Bold "Tw Cen MT Bold" [en]
  • TwCenMT-Italic "Tw Cen MT Italic" [en]
  • TwCenMT-BoldItalic "Tw Cen MT Bold Italic" [en]

iOS 6 or 7で「游ゴシック体」や「ヒラギノ丸ゴシック」を使う

20131027032814


OS XではMarvericks (10.9) から「游ゴシック体」と「游明朝体」が標準搭載されたことで話題になりましたが、実はiOSでも6以上からこれらのフォントが使用できます。


iOS 6から追加ダウンロードフォントという仕組みが導入され、游ゴシック体などのフォントはその追加ダウンロードフォントという形で提供されています。
アプリケーションからは必要に応じてフォントをダウンロードして利用します。

フォント一覧

iOS 6で使えるフォントはアップルの下記のページに記載されています。
このページの下の方に追加情報として記載されているフォントがダウンロードフォントになります。
iOS 6:フォントリスト (http://support.apple.com/kb/HT5484?viewlocale=ja_JP&locale=ja_JP)


上記のページに記載されている追加ダウンロードフォントのうち、日本語のフォントは以下の8つです。

  • Hiragino Kaku Gothic Std W8 (ヒラギノ角ゴ Std W8)
  • Hiragino Kaku Gothic StdN W8 (ヒラギノ角ゴ StdN W8)
  • Hiragino Maru Gothic Pro W4 (ヒラギノ丸ゴ Pro W4)
  • Hiragino Maru Gothic ProN W4 (ヒラギノ丸ゴ ProN W4)
  • YuGothic Bold (游ゴシック体 ボールド)
  • YuGothic Medium (游ゴシック体 ミディアム)
  • YuMincho Demibold (游明朝体 デミボールド)
  • YuMincho Medium (游明朝体 ミディアム)


このうちヒラギノに関してはProNとPro、StdNとStdの違いで2書体になってるので実質6書体ですね。
ヒラギノ丸ゴシックやヒラギノ角ゴシックのW8などは標準搭載のフォントとはかなり趣きが異なるので使いどころによって重宝します。


iOS 7ではさらに少しフォントが追加されているのですがそれについては別の記事で記載します。

APIについて

追加ダウンロードフォントを利用するには、必要に応じてダウンロードの処理をします。
ダウンロードには`CoreText.framework`の下記のAPIを利用します。

bool CTFontDescriptorMatchFontDescriptorsWithProgressHandler(
    CFArrayRef                          descriptors,
    CFSetRef                            mandatoryAttributes,
    CTFontDescriptorProgressHandler     progressBlock) CT_AVAILABLE(10_9, 6_0);

このAPIはドキュメントに解説が載っていないので、利用方法はヘッダファイルを見ます。


また、アップルから公式のサンプルコードが提供されていますのでこちらも参考になります。
DownloadFont - iOS Developer Library (https://developer.apple.com/LIBRARY/IOS/samplecode/DownloadFont/Introduction/Intro.html)


情報は少ないですが、特に難しいことはなく、1番目の引数にFont Descriptorの形でフォント情報を渡すと、適宜ブロックのコールバックが呼ばれるという仕組みです。


追加ダウンロードフォントについては、フォント名がわかっているのでフォント名からFont Descriptorを作ります。

NSDictionary *attributes = @{(id)kCTFontNameAttribute: fontName};
CTFontDescriptorRef fontDescriptor = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)attributes);
NSArray *fontDescriptors = @[(__bridge id)fontDescriptor];
CFRelease(fontDescriptor);


そして、Font Descriptorを先ほどのAPIに渡せば、必要に応じてフォントをダウンロードし、アプリケーションで利用できるように登録するところまでやってくれます。
ダウンロードの進捗やトータルのサイズなどの情報はブロックの引数の`progressParameter`に入っています。
アプリケーションでダウンロード中のプログレスバーなどを表示するにはこのパラメータの値を利用します。


ダウンロードされたフォントはアプリケーションごとに保存されるわけではなく、すべてのアプリケーションで共有の場所に保存されます。
例えば下記の場所です。

file:///private/var/mobile/Library/Assets/com_apple_MobileAsset_Font/83f5ce0efa7a810b73a7231c0e107f2955f2c85c.asset/AssetData/Yu%20Gothic%20Bold.otf


つまり、あるアプリケーションでフォントがダウンロードされていれば(例えばiBooksなど)、別のアプリケーションではダウンロード処理はスキップしてそのフォントを利用できます。
ただ、一度ダウンロードされたフォントでもデバイスの空き容量によって削除されることがあるので、プログラムはそれを考慮して作成する必要があります。
また、すでに他のアプリケーションでフォントがダウンロードされていても、自分のアプリケーションで利用可能にするには上記のAPIを呼ぶ必要があります。
さらに、どのアプリケーションでダウンロードしたかにかかわらず、一度アプリケーションを終了すると利用登録が解除されてしまうので、次に起動したときには再度上記のAPIを呼ぶ必要があります。


まとめると、

  • 追加ダウンロードフォントはデバイス全体で共有される。
  • ダウンロードされたフォントは、自動的にシステムから削除されることがある。
  • フォントがダウンロード済みならダウンロード処理は自動的にスキップされる。
  • フォントが他のアプリケーションによってダウンロード済みであっても、自分のアプリケーションで利用可能にするにはこのAPIを呼ぶ必要がある。
  • 自分のアプリケーションでダウンロードしたフォントであっても、アプリケーションを終了したら利用登録が解除されるので、再度利用可能な状態にするにはこのAPIを呼ぶ必要がある。


ややこしいように感じますが、ダウンロードのAPIは必要ならダウンロードされ、ダウンロード済みならダウンロードはスキップされてロード処理だけをする、という動きをするので、要するに最初に利用しようとしたときにダウンロードのAPIを呼べばいいということになります。
ただ、単純にダウンロードのAPIを毎回呼ぶとすると、ダウンロードしたくないときにもダウンロードされてしまうので、必要に応じてダウンロードはキャンセルできるようにしたほうがいいでしょう。

フォントのダウンロードおよび利用登録

追加ダウンロードフォントを利用可能にする処理は下記のようになります。
ダウンロードの処理はフォントがどのアプリケーションによってもダウンロードされてないときのみ行われます。
フォントがダウンロード済みであっても、アプリケーションを起動しただけの状態では利用可能になっていないので、この処理をする必要があります。その場合、コールバックはダウンロードのステータスになることはなく、短時間で`kCTFontDescriptorMatchingDidFinish`の状態になります。

CTFontDescriptorMatchFontDescriptorsWithProgressHandler((__bridge CFArrayRef)fontDescriptors, NULL, ^bool(CTFontDescriptorMatchingState state, CFDictionaryRef progressParameter) {
    NSDictionary *parameter = (__bridge NSDictionary *)progressParameter;
    double progressValue = [parameter[(id)kCTFontDescriptorMatchingPercentage] doubleValue];
    
    if (state == kCTFontDescriptorMatchingDidBegin) { // 処理の開始に1度だけ呼ばれる
        dispatch_async( dispatch_get_main_queue(), ^ {
            // ダウンロードはサブスレッドで行われるのでUIの更新などはメインスレッドで行う
            ...
        });
    } else if (state == kCTFontDescriptorMatchingDidFinish) { // 処理の終了時に1度だけ呼ばれる
        dispatch_async( dispatch_get_main_queue(), ^ {
            UIFont *font = [UIFont fontWithName:fontName size:1.0f];
            // この時点でフォントが利用可能になる
            ...
        });
    } else if (state == kCTFontDescriptorMatchingWillBeginDownloading) {
        // フォントが未ダウンロードの場合のみ、ダウンロードの開始前に呼ばれる
        ...
    } else if (state == kCTFontDescriptorMatchingDownloading) {
        // ダウンロード中、ダウンロードの進捗によって適宜呼ばれる
        ...
    } else if (state == kCTFontDescriptorMatchingDidFinishDownloading) {
        // ダウンロード完了時に呼ばれる
        ...
    } else if (state == kCTFontDescriptorMatchingDidFailWithError) {
        // ダウンロードが失敗したときに呼ばれる
        ...
    }
    
    return (bool)YES;
});


ダウンロード済みのフォントがある場合にフォントのロードだけ行い、ダウンロードはしたくないという場合は、例えば下記のようにします。

CTFontDescriptorMatchFontDescriptorsWithProgressHandler((__bridge CFArrayRef)fontDescriptors, NULL, ^bool(CTFontDescriptorMatchingState state, CFDictionaryRef progressParameter) {
    if (state == kCTFontDescriptorMatchingDidFinish) {
        dispatch_async( dispatch_get_main_queue(), ^ {
            UIFont *font = [UIFont fontWithName:fontName size:1.0f];
            if (font) {
                if ([self.delegate respondsToSelector:@selector(fontDownloaderDidFinish:fontName:)]) {
                    [self.delegate fontDownloaderDidFinish:self fontName:fontName];
                }
            }
        });
    } else if (state == kCTFontDescriptorMatchingWillBeginDownloading) {
        return (bool)NO;
    }
    
    return (bool)YES;
});


`kCTFontDescriptorMatchingWillBeginDownloading`で`NO`を返しているのでそれ以上の処理は行われません。
もしダウンロード中のキャンセルをサポートする場合にも同様にすることで実現できます。


アップルのサンプルコードは主に中国語フォントを使っているので、日本語フォントについて簡単に試せるようにしたコードをGithubで公開しているので、よかったらご覧ください。

kishikawakatsumi/DownloadFont · GitHub

チュートリアルなどで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 のほうは半日くらいでちょちょっと書いただけなのでおかしなところが結構あると思うので見つけたら教えてください。