Placeholder 付き UITextView

UITextView って UITextField みたいに Placeholder がないんですね。つい先日まで気づきませんでした。必要とするようなシチュエーションがないとかそのような UI が iOS 的にナシなのかなぁと思っていたらカレンダーアプリのイベント追加画面で Apple 自身が実装してました。しょうがないので作りました。良かったら使ってみてください。CSTextView から CSNPlaceholderTextView に名称を変更しています。

CSNPlaceholderTextView

CSNPlaceholderTextView

特徴

  • 挙動は Calendar.app の イベント追加画面 (EKEventEditViewController)のメモの部分に似せている
  • placeholder の表示位置は Caret の位置に自動調整
  • placeholder の font は textView 側の font プロパティに連動
  • UITextView のリプレイスとして使えるように UITextViewDelegate を汚染しない

ということで、Placeholder として使っている Label の textColor は UITextField の placeholder プロパティの説明にあるように

The placeholder string is drawn using a 70% grey color.

な色にしてあったり、UITextViewDelegate を汚染しないように UITextiView が準拠している UITextInput プロトコルのメソッドをオーバーライドするようにして表示位置や出し入れを調整しています。

ハマったのはplaceholder の表示の On/Off を

- (CGRect)caretRectForPosition:(UITextPosition *)position

だけでやっていましたが、入力済みのテキストを Cut してから Undo Cut して Undo Typing したあとに placeholder が消えないことでした。

- (UITextRange *)selectedTextRange

メソッドはこの問題の動作でも呼ばれることがわかったのでここでも On/Off を行うことで回避しました。ただ、この方法が UITextInput とかのテキスト入力系 の振る舞いとして適切かどうかはちょっとわかりません。

追記

「text プロパティでテキストを代入すると placeholder が消えない」というバグがあったので修正しておきました。

CocoaPods 対応

pod 'CSNPlaceholderTextView', '~> 0.0'

Mountain Lion (Mac OS X 10.8) でマウスカーソルのサイズを標準機能で変える方法

プレゼン等をする際に通常使っているマウスカーソルの大きさでは小さく見えにくい場合があります。調べてみると Mac の標準機能でできるようですが、Mountain Lion (Mac OS X 10.8) から設定場所がちょっと変わったのでメモ。

設定場所

Mac のシステム環境設定のアクセシビリティの中の

f:id:griffin-stewie:20130217222405p:plain

"ディスプレイ" の中に "カーソルサイズ" という項目があります。

f:id:griffin-stewie:20130217222403p:plain

これでカーソルサイズが特大になります。

カーソルサイズを最大にすると細かい物のクリックがしにくいのでプレゼン時のデモ等ではお気をつけください。

iOS6以降でのMapアプリの起動方法

はじめに

ここでは、アプリ内部で使用する Map 機能(MapKit)のことは特に言及せず、アプリから外部の Map アプリ(標準 Map.app と GoogleMap.app)の起動について書きます。

現状

標準 Map.app と GoogleMap.app

iOS 6 以前までは純正の Map.app は内部では Google が使用されていました。iOS 6 からは Apple 独自に切り替えられました。2013/02/15 時点では改善をされてきてはいるものの以前の Google ベースの Map.app と比べると内容的に見劣りする状況です。そこで Google から 3rd Party アプリとして GoogleMap.app がリリースされました。このアプリは標準インストールされていたころの Map.app よりもパワーアップしていて評判の高いものになっています。

ニーズ

こうなるとやはりニーズとしては以下のようになります。

  • 使いやすい GoogleMap.app を呼び出して使いたい
  • GoogleMap.app をインストールしていないユーザー向けにも考慮して標準 Map.app も使いたい

対応方法

GoogleMap.app を開く挙動に関して

  • iOS バージョン問わず
    • GoogleMap.app がインストールされているかを canOpenURL: で確認し、GoogleMap.app で今まで同じような挙動をさせることができました。

iOS 標準 Map.app を開く挙動に関して

  • iOS 6 以前の場合
    • Map.app は内部実装が Google のものなので昔と同じ挙動(既存の実装)で Map.app を開く
  • iOS 6 以降の場合
    • 比較的単純なパラメータなら iOS 6 以前と同じでも問題はないが一部以前まで使えていたパラメータが使えないものもある
    • iOS 6 から追加された MKMapItem クラスの機能を使ってより細かいオプションを設定して iOS 標準 Map.app (Apple) を開く

コード例

iOS の標準 Map.app を開く

    /**
     MKMapItem が使えるか確認し、使える場合はそれを利用(事実上 iOS 6 以前か以降かの判定)
     使えない場合(事実上 iOS 6.x 以前)の場合は昔からの挙動
     */
    Class itemClass = [MKMapItem class];
    if (itemClass) {
        /// MKPlacemark を作る
        CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(self.place.latitude, self.place.longitude);
        MKPlacemark *placemark = [[MKPlacemark alloc] initWithCoordinate:coordinate addressDictionary:self.place.addressDictionary];
        
        /// MKPlacemark から MKMapItem を作る
        MKMapItem *item = [[MKMapItem alloc] initWithPlacemark:placemark];
        item.name = self.place.name;
        
        /// Apple Map.app に渡すオプションを準備
        /// Span を指定して Map 表示時の拡大率を調整
        MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(coordinate, 250, 250);
        MKCoordinateSpan span = region.span;
        
        /// Apple Map.app を開く
        BOOL result = [item openInMapsWithLaunchOptions:@{
                             MKLaunchOptionsMapSpanKey : [NSValue valueWithMKCoordinateSpan:span],
                           MKLaunchOptionsMapCenterKey : [NSValue valueWithMKCoordinate:coordinate]
                       }];
        
        if (result == NO) {
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
                                                            message:@"Apple Map.app を開けませんでした"
                                                           delegate:nil
                                                  cancelButtonTitle:@"閉じる"
                                                  otherButtonTitles:nil];
            [alert show];
        }
    } else {
        NSString *url = [NSString stringWithFormat:@"http://maps.apple.com/?ll=%f,%f&q=%@", self.place.latitude, self.place.longitude, self.place.escapedName];
        NSURL *URL = [NSURL URLWithString:url];
        [[UIApplication sharedApplication] openURL:URL];
    }

MKMapItemopenInMapsWithLaunchOptions: を使うとある程度細かい指定を Apple Map.app に対して渡せます。openInMapsWithLaunchOptions: で span をしていすると表示された時の大きさ(ズーム具合)を調整できます。MKCoordinateSpan の値は文系の私にはよく分からない感じです。実際の利用シーン的には「任意の地点を軸に 500m くらいの範囲で表示したい」というのが多いと思います。このメートルで指定するには球体である地球を考慮してうんぬんかんぬんと私にとってはよく分からない計算をしないといけないです。簡単に済ませる方法としては MKCoordinateRegionMakeWithDistance 関数を使って得られる MKCoordinateRegion から MKCoordinateSpan を取得するのが簡単でわかりやすいです。

Google Map.app を開く

こちらを参考に URL Scheme を使って Google Map.app を開く。

    NSString *url = [NSString stringWithFormat:@"googlemaps://?q=%f,%f(%@)", self.place.latitude, self.place.longitude, self.place.escapedName];
    NSURL *URL = [NSURL URLWithString:url];
    if ([[UIApplication sharedApplication] canOpenURL:URL]) {
        [[UIApplication sharedApplication] openURL:URL];
    } else {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
                                                        message:@"Google Map.app がインストールされていません"
                                                       delegate:nil
                                              cancelButtonTitle:@"閉じる"
                                              otherButtonTitles:nil];
        [alert show];
    }

サンプルコード

ここにサンプルコードをおいておきます。

iOS で「LINE で送る」を実装する

iOS で「LINE で送る」を実装する

昨年末頃、LINE より公式に「LINE で送る」の仕様が公開されました。私の知る範囲では Objective-C で書かれた iOS 向けのライブラリは以下の2つです。

LineKit

実装されている内容

  • LINE.app のインストール確認
  • テキストを送る
  • 画像を送る

上記の通り非常にシンプルなライブラリになっています。

LineActivity

実装されている内容

  • LINE.app のインストール確認
  • 未インストールの場合 App Store を開く
  • テキストを送る
  • 画像を送る

UIActivity のサブクラスとして実装されています。アイコン用の画像ファイルも添付されています。

練習がてら自分も作ってみた

CSNLINEOpener

実装されている内容

  • LINE.app のインストール確認
  • 未インストールの場合 App Store を開く
  • テキストを送る
  • 画像を送る
  • 単機能クラスとUIActivityの二本立て

LINE アプリに対して文字列もしくは画像を送る CSNLINEOpener クラスと UIActivity のサブクラス CSNLINEOpenerActivity があります。ちょうど LineKitLineActivity の間ですね。

CSNLINEOpenerActivity の方はアイコン画像を用意できないのでイニシャライザで渡してもらうような形にしています。

とはいえ、私自身 LINE ユーザーじゃないので送信テストまではできてませんw ごめんなさい。

ご利用には ガイドライン に従う必要があります。

インストール

CocoaPods で

pod 'CSNLINEOpener', '~> 0.0'

ハマった・迷った・困ったポイント

実装例や既存ライブラリが探しにくい

"LINE" というキーワードが一般的すぎてなかなか探しにくいです。最終的には具体的なメソッド名やスキーマ名等で探したら見つかったのが上述の2つのライブラリです。

UIPasteboard を使った UIImage の渡し方

UIPasteboard を使って UIImage を渡すんですが、

pasteboard.image = image;

とかすると LINE 側で落ちます。 結論から言うと

[pasteboard setData:UIImagePNGRepresentation(image) forPasteboardType:@"public.png"];

としないとダメのようです。プロパティで渡せたらもうちょっと楽だったんですが。

画像の共有方法変更

iOS 7 から同一チーム ID のアプリ間でしか名前付き Pasteboard を使えなくなっています。最新の CSNLINEOpener では generalPasteboard を使うように変更しています。これにより画像の共有は可能ですがユーザーがコピーしていたデータは上書きされて消えるのでご注意ください。

UIActivity の使い方

私のドキュメントの読み間違いだったら申し訳ないんですが、私の理解はこんな感じです。

  • ViewController を使う系か使わない系か?
    • 使う系の場合
      • activityViewController を実装して ViewController を返す
    • 使わない系の場合
      • performActivity を実装する
  • いずれの場合も処理完了時には activityDidFinish: を呼ぶ
  • prepareWithActivityItems: は data への参照を確保したり表示させたい ViewController の準備など、あくまでも自身が提供したい動作の準備のみを行う

たまに上記の私の理解とは違う実装をしているのを見かけますが、とりあえずは今回は自分の解釈で実装しました。

LINE のアイコン

UIActivity として提供する場合アイコンも一緒に配布したいわけですが、LINE の公式に配布されている画像リソースはそのままでは使いにくいですし、改変は NG っぽい。それに勝手に同梱するわけにもいかなそう。ということでこのライブラリを使う人に自分で用意してもらう形にしてみました。

まとめ

LINE で送る機能を作るのは簡単ですし、わざわざ自分で作らなくても既存のものを使わせてもらえばいいのですが、簡単な故に良い練習材料かなぁと思いました。気が向いたら使ってみてください。

特定のアプリが触ったファイルを監視

@norio_nomura さんに教えてもらったことを忘れないための備忘録。

sudo fs_usage -f pathname <プロセスID>

とかするとこんな感じで出力される。

20:11:32  stat64            ple.Safari/Webpage Previews/FFEE0AA49397DF9D900BD8B88BA37224.jpeg    0.000003   Safari      
20:11:32  stat64            pple.Safari/Webpage Previews/FFEE0AA49397DF9D900BD8B88BA37224.png    0.000003   Safari      
20:11:32  statfs64          /Users/Stewie/Library/Caches/com.apple.Safari/Webpage Previews       0.000004   Safari      
20:11:32  statfs64          /Users/Stewie/Library/Caches/com.apple.Safari/Webpage Previews       0.000002   Safari      
20:11:32  fsctl             /Users/Stewie/Library/Caches/com.apple.Safari/Webpage Previews       0.000003   Safari      
20:11:32  lstat64           /Users/Stewie/Library/Safari/HistoryIndex.sk                         0.000025   Safari      
20:11:32  stat64            /Users/Stewie/Library/Safari/HistoryIndex.sk                         0.000004   Safari      
20:11:32  open              /Users/Stewie/Library/Safari/HistoryIndex.sk                         0.000012   Safari      
20:11:32  open              orks/CoreFoundation.framework/Versions/A/Resources/tokruleLE.data    0.000022   Safari      
20:11:32  stat64            /usr/lib/libmecab.1.0.0.dylib                                        0.000007   Safari      
20:11:32  open              /usr/lib/libmecab.1.0.0.dylib                                        0.000011   Safari      
20:11:32  close                                                                                  0.000005   Safari      
20:11:32  stat64            /usr/share/tokenizer/ja                                              0.000007   Safari      
20:11:32  open              /Users/Stewie/.mecabrc                                               0.000016   Safari

NSDateFormatter で YYYY を使っちゃだめ

ダメってことはないです。ただ、私たちが通常使っている概念と違ってくるので普通は使わないよねって話です。

NSDateFormatterのYYYY利用時の注意点 - 風日記 からの引用

Y(大文字)はその週の年、つまり1月1日が週の後半(厳密には木曜日以降)だったら、その週は前年の週と見なされる。

Data Formatting Guide: Date Formatters によると Unicode のルールではそうなるらしいです。 NSDateFormatter は OS バージョンによってベースにしているルールのバージョンが違うので注意。

UTS #35: Unicode Locale Data Markup Language

Xcode の Organizer でスクショの Diff がみれる

今日の出来事なのですが、Xcode の Organizer がいつの間にか進化していることに気がつきました。

下の画像は Oarganizer 上でスクリーンショットを1つ選択したところです。特になんてこともない状態です。

f:id:griffin-stewie:20130115230523j:plain

下の画像が Oarganizer 上でスクリーンショットを1つ選択したところです。よく見ると下のツールバーの "Compare" チェックボックスがアクティブになっています。

f:id:griffin-stewie:20130115230529j:plain

下の画像が Compare のチェックを On にしたところです。差異が無いところは黒く塗りつぶされています。

f:id:griffin-stewie:20130115230534j:plain

この機能を使えばレイアウトの微調整や表示コンテンツの間違い探しが簡単になりますね。