Placeholder 付き UITextView
UITextView って UITextField みたいに Placeholder がないんですね。つい先日まで気づきませんでした。必要とするようなシチュエーションがないとかそのような UI が iOS 的にナシなのかなぁと思っていたらカレンダーアプリのイベント追加画面で Apple 自身が実装してました。しょうがないので作りました。良かったら使ってみてください。CSTextView から 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'
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]; }
MKMapItem
の openInMapsWithLaunchOptions:
を使うとある程度細かい指定を 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 のサブクラスとして実装されています。アイコン用の画像ファイルも添付されています。
練習がてら自分も作ってみた
実装されている内容
- LINE.app のインストール確認
- 未インストールの場合 App Store を開く
- テキストを送る
- 画像を送る
- 単機能クラスとUIActivityの二本立て
LINE アプリに対して文字列もしくは画像を送る CSNLINEOpener
クラスと UIActivity のサブクラス CSNLINEOpenerActivity
があります。ちょうど LineKit と LineActivity の間ですね。
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 バージョンによってベースにしているルールのバージョンが違うので注意。