Attention! Translated article might be found on my English blog.
ラベル Objective-C の投稿を表示しています。 すべての投稿を表示
ラベル Objective-C の投稿を表示しています。 すべての投稿を表示

2017年10月16日月曜日

自作.frameworkでの文字列のローカライズ

通常文字列のローカライズする(Localizable.stringsに書かれた文字列を使う)には、

NSLocalizedString(@"Stop", @"");

を使いますが、このマクロの定義は

#define NSLocalizedString(key, comment) \
    [NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:nil]

となっています。
結果、これを自作.frameworkで使うと利用プログラム側のbundleが参照されることになり、.framework側が参照されません。

解決策としては

NSLocalizedStringFromTableInBundle(@"Stop", nil, [NSBundle bundleForClass:self.class], @"");

てな感じでNSLocalizedStringFromTableInBundle()というマクロを使い、
クラスが属するバンドルを参照するのが良さそうです。

2016年7月21日木曜日

Obj-CでJSONをNSArrayやNSDictionaryとして読み込む

-[NSJSONSerialization JSONObjectWithData:options:error:]を使う。

参考: iOSでjson | Professional Programmer

例えば

NSData *json = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"hoge" ofType:@"json"]];
    
id dic = [NSJSONSerialization JSONObjectWithData:json
                                             options:0
                                               error:nil];

返り値はNSArrayかNSDictionaryになるようなので、中身が不定なJSONの場合は
クラスをチェックする必要がありそうです。

2015年8月7日金曜日

単純なCプログラムからObjCのFoundation KitやApplication Kitを使う

Foundation KitやApplication Kitのちょっとした動作確認に便利な方法です。

こんな感じで単純なCのコードからObjCを使いたい場合、
#import <Foundation/Foundation.h>

int main( int argc, char** argv ) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    NSLog(@"%@", storage.cookies);
    [pool release];
    return 0;
}

 こんな感じでMakefileを書いて

CC = gcc

SharedCookie: main.m
    $(CC) -Wall -std=c99 main.m -o SharedCookie -framework Foundation

makeするとビルドできます。

ポイントは

  • ソース中ではクラスを使う前にNSAutoreleasePoolオブジェクトを生成しておく
  • ビルド用コマンドでは-frameworkでframework名を指定する

あたりでしょうか。
gcc使ってますけど最近はxcodebuildとかなんだろうか。
ちょっと古い方法かもしれませんがあしからず。

2015年7月27日月曜日

WebViewの表示内容を.webarchiveとして書き出す

[[[[webView mainFrame] DOMDocument] webArchive] data]でNSDataに変換し、
writeTo**で書き出すのが簡単なようです。

    NSData *data = [[[[self.webView mainFrame] DOMDocument] webArchive] data];
    BOOL success = [data writeToFile:path atomically:YES];

他にも色々アプローチがある模様。
参考: How do you save to WebArchive webView editable content?
参考: webArchive | raizan2ame

自作アプリで保存した.webarchiveがSafariで読めない場合は
自作アプリで保存した.webarchiveファイルが開けないも参考にしてみてください。

2015年5月28日木曜日

Safariと自作アプリでセッション(cookie)を共有する方法を調べた

2016/10/01 追記: 10.11から、アプリケーション間でcookieを共有できなくなった様です。
NSHTTPCookieStorage.hのsharedHTTPCookieStorageのdiscussionによると、
Starting in OS X 10.11, each app has its own sharedHTTPCookieStorage singleton, 
which will not be shared with other applications.
とのこと。むう…ユーザにいちいちログインしてもらったりするのはフィッシングサイト(アプリ)だと疑われそうで嫌ですね…。
2015/08/07 追記:WebViewを使えばSafariとセッションを共有できているようです。
共有するような設定ってどっかに有ったっけか…。
基本的に何もしなくても共有されるような気がします。
[NSHTTPCookieStorage sharedHTTPCookieStorage]というのが使えそう。

参考: Safari 5.1 cookie format specs - Stack Overflow
参考: NSHTTPCookieStorage Class Reference

なお、Safariのクッキーは~/Library/Cookies内で保存している模様。

参考: (旧) Cocoaの日々: Safari のクッキーはどう保存されているのか?

但し、現在はplistではなく、.binarycookiesというフォーマットのようです。
.binarycookiesは普通のバイナリですが、解析用スクリプトが色々あるようで、
こちらからもアプローチできそうです。

参考: Another Forensics Blog: Safari Binary Cookies - Now with more parsing power!
参考: Safari/iOS - Cookies.binarycookies reader

2015年5月10日日曜日

WebViewでのconsole.log()の内容を取得する

WebView上でconsole.log()を実行した時にWebFrameLoadDelegateのdoConsoleLog:を実行させたい場合を考えます。大雑把な手順と実装例は以下のとおりです。


1. WebViewにWebFrameLoadDelegateをセットする

  詳細割愛。

2. delegateで-[WebFrameLoadDelegate webView:didClearWindowObject:forFrame:]を実装する

- (void)webView:(WebView *)webView didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame {
    [windowObject setValue:self forKey:@"console"];
}
おそらくJS上でdelegateをconsoleとして扱うよう設定しているんだと思います。setValue:forKey:はKVC由来でしょうか。なお、CallJSではwebView:windowScriptObjectAvailable:で同様の処理を行っていますが、10.4でdeprecatedになっているようです。

3. delegateで+[NSObject(WebScripting) isSelectorExcludedFromWebScript:]を実装する

+ (BOOL)isSelectorExcludedFromWebScript:(SEL)selector {
    return selector != @selector(doConsoleLog:);
}
doConsoleLog:だけJSから実行可能にしているんだと思います。

4. delegateで+[NSObject(WebScripting) webScriptNameForSelector:]を実装する
+ (NSString *) webScriptNameForSelector:(SEL)sel {
    return (sel == @selector(doConsoleLog:)) ? @"log" : nil;
}
こちらはJSとObjCとの間でメソッドを関連づけているのだと思います。

5. delegateでconsole.log()実行時に呼ばれるメソッドを実装する
- (void)doConsoleLog:(NSString *)message {
    NSLog(@"message:%@", message);
}
console.log()の引数がNSStringとして渡されてきます。

以上でconsole.log()をNSLog()で表示することが出来るようになります。
少しだけObjC-JS間のデバッグが楽になるんじゃないでしょうか…!

参考: CallJS - Apple Developer

2015年5月9日土曜日

Objective-CからJavaScriptに複雑な文字列を渡したかった

結論から言うと、UTF8でURLエンコードとBase64エンコードを重ねて対処しました。

objc側:

- (void)updateWithHTML:(NSString *)html {
    html = [html stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    
    NSData *data = [html dataUsingEncoding:NSUTF8StringEncoding
                      allowLossyConversion:NO];
    
    NSString *base64String = [data base64EncodedStringWithOptions:0];
    
    NSString *jsString = [NSString stringWithFormat:@"updateWithHTML('%@');", base64String];
    
    NSString *result = [self.webView stringByEvaluatingJavaScriptFromString:jsString];
    
    NSLog(@"result:%@", result);
}
JS側:
function updateWithHTML(base64html) {
    var html = decodeURIComponent(window.atob(base64html));
    
    // do some stuff
    
    return 'OK';
}


Base64エンコードを使う理由:
文字列に含まれる括弧やシングルクォートのせいか、JSを実行できていないようでした。
(objcコード中の引数htmlは特定ページを表現するための完全なHTML文字列で、JSコードも含んでいます)
JSON化も試してみたのですが、いまいち期待通り動かなかったためBase64化して渡すことにしました。
うまく使えばJSONでも解決できそうなのですが…必要があればまた検討します。

URLエンコードを使う理由: 
URLエンコード無しの場合、JS側でBase64デコード後の日本語が文字化けしてしまいました。
window.btoa - Web API インターフェイス | MDNによると、Unicodeに対してatob()を使うと例外が出るようでした。
私の書いたJSコードをSafariで動かしてみても特に例外は出ていないようでしたが、
上記サイトの通りURLエンコードも行うようにしたところ、期待通り日本語文字列を取得できるようになりました。

なお、ios - How do I URL encode a string - Stack Overflowによると、[NSString stringByAddingPercentEscapesUsingEncoding:]は不完全らしいので、さらに改善が必要かもしれません。