フラットデザインに馴染むViewのつくりかた

2014年8月21日木曜日 ミライ

はじめまして、ミライです。
iPhoneアプリの開発を担当しています。

出てきた当初はどうなることかと思ったiOS7のフラットデザインでしたがすっかり広まりましたね。
自前でつくったビューもあのフラットデザインに馴染むようにしたーい。ということで、
iOS7のUIActionSheetやUIAlertView風の、端だけ角丸になったリストビューを作ってみました。
今回はそのポイントをかんたんにご紹介します!


こんな感じのものをつくります↓

UIViewを継承したクラスをつくって、そのinitの中でビューをつくっていく想定です。
ここでは実際に使うときに必要になる諸々は割愛させていただき、ボタン部分をつくる際のポイントだけご説明します。

というわけでinitの中身を書いていきます。
まず背景を置きます。大きさは画面一杯、色は黒でalphaを0.3〜0.4くらいにするとUIActionSheetなどと近い雰囲気になります。

self = [super initWithFrame:[[UIScreen mainScreen] bounds]];
if (self) {
    self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.4];
    //ここに足していくよ
}
return self;
ボタン以外の部分が押されたらビューをキャンセルするようにしたい場合、背景にUITapGestureRecognizerを貼ってビューをremoveするメソッドを呼び出すようにしますが、ここでは割愛させていただきます。

この上に、ボタンを一定の間隔で並べます。
大きさや色は次のようにしました。
・並べる間隔は44px毎
・画面端との距離は各8px
・ボタンの背景色は white、alphaは0.95くらい
 文字色は red:0.0 green:0.5 blue:1.0 くらい

for(int i = 0; i < 3; i++){
    UIButton *btn = [UIButton buttonWithType: UIButtonTypeCustom];
    btn.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.95];
    btn.titleLabel.font = [UIFont systemFontOfSize:20];
    [btn setTitleColor:[UIColor colorWithRed:0 green:0.5 blue:1.0 alpha:1.0] forState:UIControlStateNormal];
    [btn setTitle:[NSString stringWithFormat:@"選択肢%d", i + 1] forState:UIControlStateNormal];
    btn.frame = CGRectMake(8, 8 + i * 44, [UIScreen mainScreen].bounds.size.width - 16, 43.6);
    //ここに足していくよ
    [self addSubview:btn];
}
並べる間隔よりボタンの高さを少しだけ小さくすることで境界線をつくりますが、
これが43px(間隔が1px)とかですと意外と間が空きすぎてスッカスカな印象になります。
43.6px(間隔が0.4px)程度とほんの少し小さめにしておくとより自然になります
こんな感じ。

このままでもいいのですが、せっかくなので端を丸くしましょう。
ボタンのうち、一番上と一番下はcornerRadiusで角を丸くします
cornerRadiusの値は4くらいにするとうまいこと馴染みます。

if(i == 0){
    btn.layer.cornerRadius = 4;
    //次はここに足すよ
}
else if(i == 2){
    btn.layer.cornerRadius = 4;
    //次はここに足すよ
}

なんだかもこもこしているので、寄せます。
一番上と一番下の高さを、角を丸くした分だけ大きくします。
今回は角丸半径は4にしていますので、それぞれ4ずつ大きくします。
一番上の下側の角丸は2番目のボタンが隠してくれますが、
一番下の上側の角丸はそのまま見えている状態なので、位置を上に4ずらします。

if(i == 0){
    //次はここに足すよ
    btn.layer.cornerRadius = 4;
    CGRect frame = btn.frame;
    frame.size.height += 4;
    btn.frame = frame;
}
else if(i == 2){
    //次はここに足すよ
    btn.layer.cornerRadius = 4;
    CGRect frame = btn.frame;
    frame.size.height += 4;
    frame.origin.y -= 4;
    btn.frame = frame;
}
するとこうなります。

うーん重なりの部分が白く浮き出て気になりますね…境界線も消えてしまいました。
これを解決する方法は色々あると思いますが、ここでは大きくするまえのボタンのboundsで
CAShapeLayerをつくり、ボタンのレイヤーにマスクをかけます

表示したい領域からはみ出ている部分は切り取って非表示にしてしまうわけです。

if(i == 0){
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
    CGPathRef path = CGPathCreateWithRect(btn.bounds, NULL);
    maskLayer.path = path;
    CGPathRelease(path);
    btn.layer.mask = maskLayer;
    
    btn.layer.cornerRadius = 4;
    CGRect frame = btn.frame;
    frame.size.height += 4;
    btn.frame = frame;
    //次はここに足すよ
}
else if(i == 2){
    CGRect bounds = btn.bounds;
    bounds.origin.y += 4;
    
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
    CGPathRef path = CGPathCreateWithRect(bounds, NULL);
    maskLayer.path = path;
    CGPathRelease(path);
    btn.layer.mask = maskLayer;
    
    btn.layer.cornerRadius = 4;
    CGRect frame = btn.frame;
    frame.size.height += 4;
    frame.origin.y -= 4;
    btn.frame = frame;
    //次はここに足すよ
}

すっきりしましたね!
これだけですとボタンの文字が中央から少しだけずれていますので、
UIButtonのtitleEdgeInsetsで位置を調整します。
4大きくしましたので2くらいずらすと自然になります。

if(i == 0){
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
    CGPathRef path = CGPathCreateWithRect(btn.bounds, NULL);
    maskLayer.path = path;
    CGPathRelease(path);
    btn.layer.mask = maskLayer;
    
    btn.layer.cornerRadius = 4;
    CGRect frame = btn.frame;
    frame.size.height += 4;
    btn.frame = frame;
    btn.titleEdgeInsets = UIEdgeInsetsMake(0, 0, 2, 0);
}
else if(i == 2){
    CGRect bounds = btn.bounds;
    bounds.origin.y += 4;
    
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
    CGPathRef path = CGPathCreateWithRect(bounds, NULL);
    maskLayer.path = path;
    CGPathRelease(path);
    btn.layer.mask = maskLayer;
    
    btn.layer.cornerRadius = 4;
    CGRect frame = btn.frame;
    frame.size.height += 4;
    frame.origin.y -= 4;
    btn.frame = frame;
    btn.titleEdgeInsets = UIEdgeInsetsMake(2, 0, 0, 0);
}

完成です!

いざ、おなじみActionSheetと並べてみると…


…ちょっと白が濃いですかね。まだ微調整が必要になりそうです。
でもこの工夫だけでもこのくらい馴染むようにはなります!

<ポイントまとめ>
・すこーしだけ透かす
・角は少し丸くする
・ボタン同士の間隔は0.4pxくらい
・表示したい領域からはみ出る分はCAShapeLayerで切り取り

「これもっと簡単にできるよ」「こういう風にも使えるよ」などありましたら是非教えてください!
ではでは〜