CGGeometry 啓蒙活動
コードを用いてのレイアウト処理
iOS 6 から Auto Layout が導入され、レイアウトに関するコードを書く機会が ずいぶんと減ってきていると思いつつもなかなか減らない今日この頃。
最後の砦となるのはやはり以下の2メソッドでしょう。
//UIView - (void)layoutSubviews; //UIViewController - (void)viewDidLayoutSubviews;
上記2メソッド内でコードを用いて様々なレイアウトを行うわけですが、
今回はその中でのコーディング作法として CGGeometry をもっと使って読みやすくできるよ!
という事を知って欲しくて書きました。
CGGeometry
CGGeometry というと聞き慣れないですが、CGRect や CGPoint, CGSize などの構造体と、それを取り巻くマクロ関数を含めた総称です。
読み辛いレイアウトコード
早速ですが、僕が読み辛いと思うレイアウトコードを例として以下に挙げました。
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)があまり重要な情報でないのが理由な気がします。
これを解決する手段の一つとして、たまに見かけるのが独自のカテゴリを実装して簡単に操作できるように行う方法があります。
例えば以下。
しかし、個人的にこのようなカテゴリ拡張は好きではありません。
理由はプロジェクトに入ってきた人が毎度実装部分をみないと動作が掴み辛い点と、
デフォルトで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 があります。
詳しくは末尾にある参考資料を読んでみてください。
追記 (2014/02/03)
ささやかな啓蒙活動の結果、各界の著名人から CGGeometry やレイアウトに関するお声を頂きました。
嬉しい限りです。
カテゴリ使用等による移植性・可読性の低下への警鐘
CGRectが扱いづらい問題、移植性や可読性を犠牲にして解決する方法がまかり通ってるけど、多少小回りが利かなくてもこれが良いと思う。
— ishkawa (@_ishkawa) 2014, 1月 24
CGGeometry の非等価性
`frame.origin.x`と`CGRectGetMinX(frame)`とか`frame.origin.x + frame.size.width`とCGRectGetMaxX(frame)は等価ではないよ。たいていは等価になるけど。
— kishikawa katsumi (@k_katsumi) 2014, 1月 24
AutoLayout への応用
同じスタイル。さらにこんなブロック拡張と便利関数群で手書きでもさほど苦痛じゃない。AutoLayout使う時は Masonry使ってる。
http://t.co/UxX19bbtsm
http://t.co/LqVyu6jdbH
— stewie (@griffin_stewie) 2014, 1月 24