CoreDataのFetchRequestテンプレートで配列を指定

クライアントアプリのコード内にSQL分を書くのがどうもしっくりこないので、FetchRequestのテンプレを使うようにしている。
単純なデータを格納することが多いせいか、テンプレだけで問題になったことはない。

fetchRequestFromTemplateWithNameメソッドでテンプレ名とプレースホルダの変数を渡すのだけど、IN句を使う必要が出てきて少し悩んだ。

テンプレのSQL分には最初、field_name IN ($hoge) のような形で設定してみたのだけど勝手に括弧が外れて、 field_name IN $hoge になった。
こういうもんなのかと思い、プレースホルダにカンマ区切りの文字列を渡してみたがエラー・・・。

どうやらNSArrayをそのまま渡せばいいだけのようだ。

NSArray *ids = @[@1, @2, @3, @4];
NSDictionary *subs = @{@"hoge":ids};
NSFetchRequest *fetchRequest = [managedObjectModel fetchRequestFromTemplateWithName:@"findByIds"
substitutionVariables:subs];

何度も使う場合、テンプレを使うことでSQL文のパース処理を省くことができるので若干高速化するようだ。
NSPredicateなんてのもあるんだなぁ・・・

PSCollectionViewでハマる

Pinterest風の表示にしたいと思って色々ライブラリをあさって見たところ、PSCollectionViewという便利そうなライブラリを発見した。
iOS6以上であれば、UICollectionViewというそのものズバリのものがあるようだ。

heightForRowAtIndexメソッドの戻り値に個々の高さを指定してやることで、高さを個別に設定することも可能だった。
戻り値にCGFloatで値を返すのだけど、小数部の桁数が増えると誤差が生じて、上手くタップを認識しなくなることがあるみたい。

調べてみると、セルの大きさをNSStringFromCGRectでNSString化して管理しているようだ。

// Calculate index to rect mapping
self.colWidth = floorf((self.width - kMargin * (self.numCols + 1)) / self.numCols);
for (NSInteger i = 0; i < numViews; i++) {
NSString *key = PSCollectionKeyForIndex(i);
// Find the shortest column
NSInteger col = 0;
CGFloat minHeight = [[colOffsets objectAtIndex:col] floatValue];
for (int i = 1; i < [colOffsets count]; i++) {
CGFloat colHeight = [[colOffsets objectAtIndex:i] floatValue];
if (colHeight < minHeight) {
col = i;
minHeight = colHeight;
}
}
CGFloat left = kMargin + (col * kMargin) + (col * self.colWidth);
CGFloat top = [[colOffsets objectAtIndex:col] floatValue];
CGFloat colHeight = [self.collectionViewDataSource collectionView:self heightForRowAtIndex:i];
CGRect viewRect = CGRectMake(left, top, self.colWidth, colHeight);
// Add to index rect map
[self.indexToRectMap setObject:NSStringFromCGRect(viewRect) forKey:key];
// Update the last height offset for this column
CGFloat heightOffset = colHeight > 0 ? top + colHeight + kMargin : top;
[colOffsets replaceObjectAtIndex:col withObject:[NSNumber numberWithFloat:heightOffset]];
}

セルのタップは、UITapGestureRecognizerを使っているようで、shouldReceiveTouchメソッドで、どのセルがタップされたか判定して、didSelectメソッドが呼びされるようだ。
heightForRowAtIndexで返される高さの小数部が増えてくると、下記のrectStringと値が一致しなくなり、タップを認識しなくなる。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if (![gestureRecognizer isMemberOfClass:[PSCollectionViewTapGestureRecognizer class]]) return YES;
NSString *rectString = NSStringFromCGRect(gestureRecognizer.view.frame);
NSArray *matchingKeys = [self.indexToRectMap allKeysForObject:rectString];
NSString *key = [matchingKeys lastObject];
if ([touch.view isMemberOfClass:[[self.visibleViews objectForKey:key] class]]) {
return YES;
} else {
return NO;
}
}

解決方法は簡単で、heightForRowAtIndexで値を返す際に、小数部が増えないようにしたり、丸めてやればいい。

- (CGFloat)collectionView:(PSCollectionView *)collectionView heightForRowAtIndex:(NSInteger)index {
return floorf(100.123456789f);
}

投稿しようとして気づいたのだけど、Objective-C(Cocoa)ネタ書いたの初めてだった。
iPhoneアプリを作っていると、ブログに書くようなネタがなかなか見つからない…
ちょっとググれば情報がたくさん見つかるし書くまでもないかなーとか思っちゃうせいだろうか。