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

jarinosuke blog

From the Nothing, with iOS

CGGeometry 啓蒙活動

コードを用いてのレイアウト処理

iOS 6 から Auto Layout が導入され、レイアウトに関するコードを書く機会が ずいぶんと減ってきていると思いつつもなかなか減らない今日この頃。

最後の砦となるのはやはり以下の2メソッドでしょう。

//UIView
- (void)layoutSubviews;

//UIViewController
- (void)viewDidLayoutSubviews;

上記2メソッド内でコードを用いて様々なレイアウトを行うわけですが、

今回はその中でのコーディング作法として CGGeometry をもっと使って読みやすくできるよ!

という事を知って欲しくて書きました。

CGGeometry

CGGeometry というと聞き慣れないですが、CGRect や CGPoint, CGSize などの構造体と、それを取り巻くマクロ関数を含めた総称です。

CGGeometry Reference

読み辛いレイアウトコード

早速ですが、僕が読み辛いと思うレイアウトコードを例として以下に挙げました。

CGRect targetFrame = self.targetView.frame;
targetFrame.origin.x = self.frame.origin.x + self.frame.size.width - 10.0;
targetFrame.origin.y = (self.frame.origin.y + self.frame.size.height - targetFrame.size.height) / 2;
self.targetView.frame = targetFrame; 

何が辛いかと言うと、targetFrame に代入される値を一つずつ読んでいられない点です。

ドット記法が4つ連なっていて、しかも間の2つ(frame.origin, frame.size)があまり重要な情報でないのが理由な気がします。

これを解決する手段の一つとして、たまに見かけるのが独自のカテゴリを実装して簡単に操作できるように行う方法があります。

例えば以下。

UIView拡張カテゴリによるUIコーディングの簡略化

しかし、個人的にこのようなカテゴリ拡張は好きではありません。

理由はプロジェクトに入ってきた人が毎度実装部分をみないと動作が掴み辛い点と、

デフォルトでCGGeometry などがあるなかで、 カテゴリ拡張を便利機能として使うのはちょっと腰が引けるのが理由です。

では、その CGGeometry を使うと上記のコードがどう読みやすくなるのかというと、

CGRect targetFrame = self.targetView.frame;
targetFrame.origin.x = CGRectGetMaxX(self.frame) - 10.0;
targetFrame.origin.y = (CGRectGetMaxY(self.frame) - CGRectGetHeight(targetFrame)) / 2;
self.targetView.frame = targetFrame; 

こんな感じになります。求めたい値がマクロ名に、対象が引数になることで かなりノイズが減った事が分かると思います。

実用例

上記までだけでも可読性とコード量の面などから充分に有益だと思います。

以下から実際の開発ユースケースに合わせて、いくつか紹介します。

ビュー座標移動

読み辛い例

CGRect frame = self.view.frame;
frame.origin.x = 10.0;
frame.origin.y = 20.0;
self.view.frame = frame;

読みやすい例

CGRect frame = self.view.frame;
self.view.frame = CGRectOffset(frame, 10.0, 20.0);

子ビューのサイズ調整

以下の様にやってしまいがちですが、驚くほどコード量が減らせるんです。

読み辛い例

CGRect childFrame = self.parentView.frame;
CGFloat horizontalPadding = 5.0; 
CGFloat verticalPadding = 10.0;
childFrame.origin.x = horizontalPadding;
childFrame.origin.y = verticalPadding;
childFrame.size.width -= horizontalPadding * 2;
childFrame.size.height -= verticalPadding * 2;
self.childView.frame = childFrame;

読みやすい例

self.childView.frame = CGRectInset(self.parentView.frame, 5.0, 10.0);

ログ出力

CGRect はデバッグ対象になる割合が非常に多いです。 その時に以下のような事をいちいち書いていたら、何をしていたか忘れかねません。

読み辛い例

NSLog(@“x:%f, y:%f, width:%f, height:%f”, self.view.frame.origin.x, self.view.frame.origin.y, self.view.frame.size.width, self.view.frame.size.height);

これも便利なマクロが提供されています。 CGRect, CGPoint, CGSize の他にも NSRange や UIEdgeInsets などたくさんの構造体に対してあります。

読みやすい例

NSLog(@“frame: %@”, NSStringFromCGRect(self.view.frame));

オブジェクトとしての扱い

CGRect などの構造体で困るのが配列などを用いて、一度に操作したいときです。 しかし、以下のようなことをしてしまうのは非常に残念な事です。

読み辛い例

NSNumber *x = [NSNumber numberWithFloat:self.view.frame.origin.x];
NSNumber *y = [NSNumber numberWithFloat:self.view.frame.origin.y]; 
NSNumber *width = [NSNumber numberWithFloat:self.view.frame.size.width]; 
NSNumber *height = [NSNumber numberWithFloat:self.view.frame.size.height];  
NSDictionary *frameDic = @{@“x”: x,
                                               @“y": y,
                                               @“width": width,
                                               @“height”: height};

CGRect を NSValue に変換する事で簡単に対応できます。

読みやすい例

NSValue *frameValue = [NSValue valueWithCGRect:self.view.frame];
CGRect frameFromValue = [frameValue CGRectValue];

参考

他にも様々な CGGeometry があります。

詳しくは末尾にある参考資料を読んでみてください。

CGGeometry : NSHipster

CGRect Tricks | Cocoanetics

追記 (2014/02/03)

ささやかな啓蒙活動の結果、各界の著名人から CGGeometry やレイアウトに関するお声を頂きました。

嬉しい限りです。

カテゴリ使用等による移植性・可読性の低下への警鐘

CGGeometry の非等価性

AutoLayout への応用