file://localhost/directory/といったURL文字列にstringByAppendingPathComponent:を使うと・・・

やっちゃ駄目って書いてあるからやるほうが悪いんですが・・


stringByAppendingPathComponent:のリファレンスには下記のように書いてあります。

Note that this method only works with file paths (not, for example, string representations of URLs).

file pathsでだけ動くと書いてありますが、表題のようにfile://localhost/....となっている文字列で実行してみると・・・

NSString *baseURLString = @"file://localhost/directory/";
NSString *fullPath = [baseURLString stringByAppendingPathComponent:@"index.html"];
NSLog(@"%@",fullPath);
// file:/localhost/directory/index.html

file:/localhost/... というように、file:の後の//が/に削られてしまいます。
stringByAppendingPathComponent:は使用せず、stringByAppendingString:を使うとか、

NSString *baseURLString = @"file://localhost/directory/";
NSString *fullPath = [baseURLString stringByAppendingString:@"index.html"];
NSLog(@"%@",fullPath);
// file://localhost/directory/index.html

ただ、stringByAppendingString:を使った場合だと、当然ながらpathを考慮されているわけではないです。
例えばpathComponentsを格納した配列から取り出して、個別に連結するといった場合には"/"を自前処理する必要があります。
文字列に%20であるとかが含まれれば、stringByReplacingPercentEscapesUsingEncoding:も必要です。

NSURLのインスタンスにして処理したほうがいいでしょう。

NSString *baseURLString = @"file://localhost/directory/";
NSURL *baseURL = [NSURL URLWithString:baseURLString];
NSString *fullPath = [[baseURL URLByAppendingPathComponent:@"index.html"] absoluteString];
NSLog(@"%@",fullPath);
// file://localhost/directory/index.html

WebViewでsetFrameをアニメーションさせようとしたら記述量が多かった

NSAnimatablePropertyContainer プロトコルのanimatorを使えば下記のような感じに書ける↓
https://developer.apple.com/library/mac/#samplecode/BasicCocoaAnimations/Listings/MainWindowController_m.html


しかしWebViewでanimatorメソッドを使ってみたら、エラーでproxyオブジェクトがselectorを呼べなかった。
ので、下記サイトのようにNSViewAnimationを明示的に生成して処理した。
http://xcatsan.blogspot.jp/2008/12/window_28.html

animatorメソッド使用で、setFrame:display:とか呼ぶ場合は、暗黙的に処理してくれるので、変更前のNSRectとかいらないが、NSViewAnimationでsetFrameをアニメーションさせる場合は、変更前のNSRect,変更後のNSRectをNSValueに変換したやつを用意して,NSViewAnimationStartFrameKey,NSViewAnimationEndFrameKeyをKeyにもつNSDictionaryを生成する必要がある。
記述量がいきなり増えますね。

第27回Rails勉強会@東北

https://www.facebook.com/events/295738007193281/

参加してきました。

参加者5名。

今回もRailsCastsを手を動かして皆でやっていった感じです。
やったのは、Deviseなどの認証系ライブラリを使わず認証をスクラッチで作るやつです。

認証↓
http://railscasts.com/episodes/250-authentication-from-scratch-revised
認証に権限をつける↓
http://railscasts.com/episodes/385-authorization-from-scratch-part-1

参加の皆様お疲れ様でした。

jQuery の複数バージョンを同時使用する

http://stacktrace.jp/jquery/with_other_lib.html#other_version


noConflictの引数にtrue渡せばいいと。

<html>
<head>
    <script type="text/javascript" src="jquery-1.2.6.js"></script>
    <script type="text/javascript" src="jquery-1.4.2.js"></script>
    <script type="text/javascript">
        // $ 関数および jQuery関数の上書きを元に戻します。
        var $j = jQuery.noConflict(true);

        // $ は jQuery ver1.2.6を参照します。
        alert($.fn.jquery);      // => 1.2.6

        // jQuery は jQuery ver1.2.6を参照します。
        alert(jQuery.fn.jquery); // => 1.2.6

        // $j は jQuery ver1.4.2を参照します。
        alert($j.fn.jquery);     // => 1.4.2
    </script>
</head>
<body></body>
</html>

noConflictって他のライブラリとの併用だけでなく、jQueryの複数バージョンもOKだったんですね。
1.8.2で実装を確認してみました。

//............................................
// Map over jQuery in case of overwrite
_jQuery = window.jQuery,

// Map over the $ in case of overwrite
_$ = window.$,
//......................................
jQuery.extend({
	noConflict: function( deep ) {
		if ( window.$ === jQuery ) {
			window.$ = _$;
		}

		if ( deep && window.jQuery === jQuery ) {
			window.jQuery = _jQuery;
		}

		return jQuery;
	},
//.........................
// Expose jQuery to the global object
window.jQuery = window.$ = jQuery;
//........................

true渡すと window.$だけじゃなく、window.jQueryも _jQueryに保持されている古いjQueryオブジェクトを参照するようにしてますね。

CoreDataのレコードをJSONに変換する

一つ前のエントリーとは逆に、Objective-CのオブジェクトをJSONへ、のパターン。
素直なNSArrayとかNSDictionaryを処理する場合には特に注意する点はなし*1
SQLiteがストアのCoreDataで,FetchしたResultのNSArrayをJSONとして渡したい場合等は注意が必要。

- (void)sampleMethod
{
  NSError *error=nil;
  NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"ModelName"];
  NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
                                      initWithKey:@"createdAt" ascending:YES];
  [request setSortDescriptors:@[sortDescriptor]];
  NSArray *fetchResult = [[self managedObjectContext] executeFetchRequest:request
                                                                error:&error];
  NSData *data=nil;
  @try {
    data = [NSJSONSerialization dataWithJSONObject:fetchResult options:NSJSONReadingAllowFragments error:NULL];
  }
  @catch (NSException *ex) {
    NSLog(@"%@",[ex name]);
    // NSInvalidArgumentException
    NSLog(@"%@",[ex reason]);
    //Invalid type in JSON write (ModelName)
  }
}

(fault)フォールティングになっているからじゃないかなーと。
それ以前にNSArrayの要素も、NSString, NSNumber, NSArray, NSDictionary以外だと駄目かもしれません。

試してみました

適当なクラスを作成して空の場合と、@propertyを定義したパターンと試してみたらInvalid type in JSON write (ClassName)だったので、単純にNSString, NSNumber, NSArray, NSDictionary以外のタイプは駄目、これらにする必要があるようです。失礼しました。リファレンスにも書いてありましたね・・。


なので、各NSManagedObjectをNSDictionaryにした配列を作成し、処理するといいかと。

- (void)sampleMethod
{
  NSError *error=nil;
  NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"ModelName"];
  NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
                                      initWithKey:@"createdAt" ascending:YES];
  [request setSortDescriptors:@[sortDescriptor]];
  NSArray *fetchResult = [[self managedObjectContext] executeFetchRequest:request
                                                                error:&error];
  NSMutableArray *mArray = [NSMutableArray array];
  for (ModelName *m in fetchResult) {
    [mArray addObject:@{@"title" : m.title,@"content" : m.content}];
  }
  NSData *d = [NSJSONSerialization dataWithJSONObject:mArray options:NSJSONReadingAllowFragments error:NULL];
  NSString *s = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding];
  NSLog(@"json:%@",s);
  //json:[{"title":"title1","content":"<span id=\"fugafuga\">hogehgoe<\/span>"},{"title":"title2","content":"second content"}]
}

こんな感じ。

*1:要素がNSString, NSNumber, NSArray, NSDictionary

JavaScriptからObjective-CへJSONで渡す

複雑な構造のはJSON使って渡せばよかったんじゃないかorz
WebViewのJavaScriptObjective-Cでのやり取りでもそりゃJSON使えるか。
JSON使うのはWebのAPI叩くときみたいな固定観念が。頭固い。


OS X 10.7から標準でNSJSONSerializationが使える。iOSは5から。

function testMethod() {
  window.objC.callFromJSObjCMethod_(JSON.stringify( [{ "a" : 1 },{ "b" : 2 }] ));
}
- (void)callFromJSObjCMethod:(NSString *)jsonString
{
  NSDictionary *dict=nil;
  NSError *error=nil;
  NSArray *array =
  [NSJSONSerialization JSONObjectWithData:
   [jsonString dataUsingEncoding:NSUTF8StringEncoding]
                                  options:NSJSONReadingAllowFragments error:&error];
  for (dict in array) {
    NSLog(@"jsObject: %@",dict); 
  }
 }

JSのObjectをNSDictionaryにしてくれるのが便利。
複雑な構造の値をJavaScriptからObjective-Cに渡す場合に使うとよさげ。
簡単な構造のものなら、JSON使わず直接Convert、と、使い分ける感じか*1

*1:WebKit Objective-C Programming GuideのUsing JavaScript From Objective-C