UITextView内のリンクをタップした時にUIActionSheetを出す
リンクやメールアドレス、住所などが含まれているUITextView内のリンクを有効にする
- InterfaceBuilderで設定。Detectionの中から必要なものにチェックを入れる
- コードで設定。dataDetectorTypesに必要なUIDataDetectorTypesを突っ込んでおけばOK
このどちらかで。
UIDataDetectorTypesの宣言はこんな感じ
enum { UIDataDetectorTypePhoneNumber = 1 << 0, UIDataDetectorTypeLink = 1 << 1, UIDataDetectorTypeAddress = 1 << 2, UIDataDetectorTypeCalendarEvent = 1 << 3, UIDataDetectorTypeNone = 0, UIDataDetectorTypeAll = NSUIntegerMax }; typedef NSUInteger UIDataDetectorTypes;
これを設定してあげると、勝手にリンクやメールを探してリンクを張ってくれます。
リンクをタップした時の挙動
ユーザがリンクをタップすると、勝手にSafariやMap、Mailなどを起動して内容を表示してくれます。便利ですね。
でも、自前のUIWebViewで開きたいとか、Safari起動するまえに確認画面出したいとか、そういった場合もあるかと思います。
リンクのタップをUITextViewDelegateで拾えるかとおもいきや、どうもUIApplicationのopenURLメソッドが呼ばれるようです。*1
仕方ないので、UIApplicationのサブクラスを作り、その中で-(BOOL)openURL:(NSURL*)urlをオーバーライドします。
実装はこんな感じ。
// TVApplication.h #import <UIKit/UIKit.h> @interface TVApplication : UIApplication<UIActionSheetDelegate> @end // TVApplication.m #import "TVApplication.h" #import "TVAppController.h" @implementation TVApplication { NSURL* _url; } - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { switch (actionSheet.tag) { // For opening url case 0: // Check url if (!_url) { break; } // Invoke super [super openURL:_url]; // Release instance variable [_url release], _url = nil; break; default: break; } } - (BOOL)openURL:(NSURL *)url { // Set url [_url release]; _url = [[NSURL URLWithString:[url absoluteString]] retain]; // Get confirmatory message NSString* message; NSString* scheme; scheme = _url.scheme; if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"] || [scheme isEqualToString:@"ftp"]) { message = @"Safariで開く"; } else if ([scheme isEqualToString:@"mailto"]) { message = @"メールを作成する"; } else if ([scheme isEqualToString:@"maps"]) { message = @"マップを開く"; } else { return NO; } // Create action sheet UIActionSheet* sheet; sheet = [[UIActionSheet alloc] init]; [sheet addButtonWithTitle:message]; [sheet addButtonWithTitle:@"キャンセル"]; sheet.cancelButtonIndex = sheet.numberOfButtons - 1; sheet.tag = 0; sheet.delegate = self; // Show sheet [sheet showInView:[TVAppController sharedController].window]; // Release sheet [sheet release]; return YES; } @end
そして、このカスタムUIApplicationをmain.mでセットしています。
// main.m #import <UIKit/UIKit.h> #import "TVAppController.h" #import "TVApplication.h" int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, NSStringFromClass([TVApplication class]), NSStringFromClass([TVAppController class])); } }
ソースを追ってみる
// Set url
[_url release];
_url = [[NSURL URLWithString:[url absoluteString]] retain];
インスタンス変数に値を突っ込んでretainしたいんだけなんだが、我ながら冗長な書き方になってしまっている気がします。
// Get confirmatory message NSString* message; NSString* scheme; scheme = _url.scheme; if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"] || [scheme isEqualToString:@"ftp"]) { message = @"Safariで開く"; } else if ([scheme isEqualToString:@"mailto"]) { message = @"メールを作成する"; } else if ([scheme isEqualToString:@"maps"]) { message = @"マップを開く"; } else { return NO; }
- (NSString*)scheme
で取ってきたスキームを見ています。今のところ、http, https, ftp, mailto, mapsは確認していますが、それ以外のスキームもあるかも知れません。
スキーム別に、アクションシートで表示するメッセージを作っています。
// Create action sheet UIActionSheet* sheet; sheet = [[UIActionSheet alloc] init]; [sheet addButtonWithTitle:message]; [sheet addButtonWithTitle:@"キャンセル"]; sheet.cancelButtonIndex = sheet.numberOfButtons - 1; sheet.tag = 0; sheet.delegate = self; // Show sheet [sheet showInView:[TVAppController sharedController].window]; // Release sheet [sheet release];
アクションシートを作るまでは至って普通かと思います。
UIActionSheetの -(void)showInView:(UIView)view に、[TVAppController sharedController].windowを渡しています。
- (TVAppController*)sharedControllerはTVAppControllerのインスタンスを返します。Xcodeのテンプレで言う、AppDelegateクラスですね。この書き方で合っているかは自信が無いです。
[sheet showInView:[self.windows objectAtIndex:0]];
と書き換えても動きますが、どうなんでしょう。まだ勉強が足りません。
ソースコード
あと、今回、githubでソースを公開してみました。
バージョン管理もまだ慣れていないんだけど、多分これで見れるはず。
TextViewLink/TextViewLink.xcodeproj at master · oropon/TextViewLink · GitHub
*1:[http://stackoverflow.com/questions/2543967/how-to-intercept-click-on-link-in-uitextview:title]