読者です 読者をやめる 読者になる 読者になる

NSBlogger

意識高いブログ

AutoLayoutとうまく付き合うコツ

xcode objectivec

AutoLayoutと仲良くなった

ぜんぜん言うこと聞かないからAutoLayout大嫌いだったんですが、接し方を変えたら言うこと聞くようになったので、そのコツを紹介します。

続きを読む

Images.xcassetsでアプリのアイコンから光沢を消す

xcode

Images.xcassets

Xcode5から画像を一元管理できる仕組みとしてImages.xcassetsが登場しました。9Sliceも簡単に設定できたり便利です。

アイコンに光沢が…

Images.xcassetsに画像を全部移行した後、アプリをインストールするとなぜかアプリのアイコンに光沢がかかってしまいました。
f:id:Kamekiti:20150314182347p:plain
設定が間違っていたようで、「iOS icon is pre-rendered」にチェックをいれると光沢がなくなりました。 

UIWebViewで特定のURLの末尾にパラメータをつける

objectivec

UIWebViewでページを読み込む前のイベントをキャッチ

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
//WebView内のロードする前に呼ばれる
}

UIWebViewDelegateの「webView:shouldStartLoadWithRequest:navigationType:メソッドを使えば、WebViewでページを読み込む前のイベントをキャッチできます。つまり、WebView内でリンクをクリックしてそのページが読み込まれる前に呼ばれます。navigationTypeでクリックしたかどうかなどを判定できます。

http, https以外のリンクは特定のアプリにとばす

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSString *urlScheme = request.URL.scheme;
    if (![urlScheme isEqualToString:@"http"] && ![urlScheme isEqualToString:@"https"] && [[UIApplication sharedApplication] canOpenURL:request.URL]) {
            [[UIApplication sharedApplication] openURL:request.URL];
            return NO;
    }
}

引数の「request」からURLを判断して、特定のアプリになげることも可能です。returnをNOにすることで、WebView内でのロードをストップさせてます。

特定のURLの末尾にパラメータを付加

- (BOOL)webView:(UIWebView *)_webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSString *url=request.URL.absoluteString;
    NSString *param = @"smartphone=1";
    NSRange extraParam=[url rangeOfString:param];
    NSRange hatena = [url rangeOfString:@"?"];
    NSRange conditionURL =[url rangeOfString:@"hogehoge.com/hoge/"];
    
    if(extraParam.location == NSNotFound && hatena.location == NSNotFound && conditionURL.location != NSNotFound){
        url = [url stringByAppendingString:[NSString stringWithFormat:@"?%@", param]];
        NSURLRequest *newRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:url]];
        [_webView loadRequest:newRequest];
        return NO;
    }else if (extraParam.location == NSNotFound && hatena.location != NSNotFound && conditionURL.location != NSNotFound){
        url = [url stringByAppendingString:[NSString stringWithFormat:@"&%@", param]];
        NSURLRequest *newRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:url]];
        [_webView loadRequest:newRequest];
        return NO;
    }
    
    return YES;
}

次に表示するURLに特定の文字列が含まれている場合、それを付加するという処理になっています。パラメータがある場合とない場合で連結する文字が「&」「?」で変わるので分岐してます。
あとはloadRequest:メソッドで文字列を付加したページを読み込みにいけばOK。

パラメータを付加しない場合もあるので、その際はそのままWebView内の処理を続ければよいのでreturn YESを返しておきます。

Watch Kit AppとiPhone App間でNSUserDefaultsを使ってデータ共有をする

objectivec watchOS

Watch Kit AppとiPhone Appのデータは別管理

基本的にWatch Kit AppとiPhone Appがそれぞれ保持するデータ領域は別々に管理されています。したがって、下記の設定を行わずにNSUserDefaultsを使ってみると、Watch Kit Appで保存したデータはWatch Kit Appからしか利用することはできません。逆もしかり。
Watch Kit AppとiPhone App間でデータ共有できたらいいなぁと考えますよね。
そんなときはApp Groupsを設定してあげればOK。

App Groupsを作る

Watch Kit App はiOSのExtentionと同じ仕組みで動作します。
iPhone Appとのデータ共有をする際は、App GroupsというIdentifierを使う必要があります。
f:id:Kamekiti:20150215234833p:plain
作り方は簡単。iOS Dev Centerの「App Groups」から新規にIdentifierを設定します。
名前は「group.jp.co.hogehoge」みたいな感じで、接頭辞に「group」をつけることが推奨されています。

App GroupsをXcode上で設定する

f:id:Kamekiti:20150215235027p:plain
XcodeのTARGETから「Capability」タブを選択し、App GroupsをONにします。
さきほど作ったIdentifierを設定すれば完了。
ちなみにこの作業はiPhone AppのTARGETとWatch KitのTARGET両方に設定する必要があります。片方にしか設定していないとデータ共有がうまく行われません。

NSUserDefaultsからデータの読み込みおよび保存

App Groupsを作ればあとは、いつものようにNSUserDefaultsを使えばデータを共有することが可能です。

データの保存

NSUserDefaults *storage = [[NSUserDefaults alloc] initWithSuiteName:@"group.jp.co.hogehoge"];
[storage setObject:data forKey:WATCH_APP_KEY];
[storage synchronize];

データの読み込み

NSUserDefaults *storage = [[NSUserDefaults alloc] initWithSuiteName:@"group.jp.co.hogehoge"];
NSData *data = [storage objectForKey:WATCH_APP_KEY];
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:data];

NSUserDefaultsでinitWithSuiteName:を使ってApp GroupsのIdentiferを使って情報取得する必要があります。

WatchKit AppからiPhone Appを起動する方法

objectivec watchOS

Watch App側の実装

[WKInterfaceController openParentApplication:@{} reply:^(NSDictionary *replyInfo, NSError *error) {}];

WKInterfaceControllerのopenParentApplication:reply:メソッドを使うことでiPhone側の親アプリを起動することができます。現在のところ、親アプリのみ起動が可能です。
一つ目の引数に渡したいデータを入れればOK。

iPhone App側の実装

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply

Watch Kit App からiPhone側の親アプリが起動されたとき、application:handleWatchKitExtensionRequest:reply:メソッドが最初に呼ばれます。渡ってきたデータをごにょごにょできますね。
URLスキームを設定していれば、特定のページへ遷移させることも可能です。

iOS Simulatorの調子が悪くなったときにやること

xcode

iOS Simulatorが調子悪いとき

たまにXcodeでアプリを実行したとき、iOS Simulatorにアプリのインストールができなくなることがあります。
f:id:Kamekiti:20150213222509p:plain
Unable to run app in Simulator. An error was encountered while running (Domain = LaunchServicesError, Code = 0)」
たとえばこういうのですね。
一体何が悪いのかわかりません。
とにかくエラーにぶち当たったことだけが警告として表示されます。

iOS Simulatorの機嫌を取り戻す

iOS Simulatorを再起動するとたいがい直りますが、それでもうまくいかないことがあったりします。そんなときは下記の手順を踏むとiOS Simulatorの機嫌がよくなります。

  1. Xcodeの「Window」→「Organizer」→「Projects」→「Derived Data」を「Delete」
  2. Xcodeの「Product」→「Clean」
  3. XcodeおよびiOS Simulatorを終了
  4. Xcodeを再起動してプロジェクトを実行

Xcodeが調子悪くて、コードは間違っていないのにビルドエラーを吐きまくるときも有効です。
iOS Simulator、iOS7 あたりからやけに不安定ですよねー。

AFNetworkingでメモリリークが起こったときの対処法

objectivec

AFNetworking

Objective C のライブラリで超ど定番なHttpClientです。いまさらながら使い始めました。AFNetworking/AFNetworking · GitHub

メモリリーク

f:id:Kamekiti:20150209233708p:plain
AFNetworkingのラッパーを作って既存のAPIとの通信をすべて置き換えました。一応メモリリークを見ておこうと思ってInstrumentsで測定したら、上記の通りメモリリークが多発してました。

解決策

@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;

[self.sessionManager invalidateSessionCancelingTasks:YES];

当然なのですが、AFHTTPSessionManagerAFURLSessionManagerでinvalidateSessionCancelingTasks:を呼んでセッションを無効化しておく必要がありました。明示的に呼んであげないと、AFHTTPSessionManagerが解放されないためメモリリークになっていました。