CALayersを使用した丸みを帯びたUIView-一部のコーナーのみ-どのように?


121

私のアプリケーションでは、次の名前の4つのボタンがあります。

  • 左上
  • 左下
  • 右上
  • 右下

ボタンの上に画像ビュー(またはUIView)があります。

ここで、ユーザーが左上のボタンをタップするとします。上記の画像/ビューは、その特定のコーナーで丸められる必要があります。

丸みを帯びた角をUIViewに適用するのが困難です。

現在、私は次のコードを使用して、各ビューに丸い角を適用しています。

    // imgVUserImg is a image view on IB.
    imgVUserImg.image=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"any Url Here"];
    CALayer *l = [imgVUserImg layer];
    [l setMasksToBounds:YES];
    [l setCornerRadius:5.0];  
    [l setBorderWidth:2.0];
    [l setBorderColor:[[UIColor darkGrayColor] CGColor]];

上記のコードは、提供されたビューの各コーナーに丸みを適用しています。代わりに、選択したコーナーに丸みを付けたかっただけです-上/上+左/下+右など。

出来ますか?どうやって?

回答:


44

iPhoneで角の丸いUILabelを作成するにどうすればよいですか?そして、どのように透明度の丸められた長方形のビューはiPhoneで行われますか?このコードを作成します。

次に、間違った質問に答えた(UIImageではなく丸められたUILabelを与えた)ことに気付いたので、次のコードを使用して変更しました。

http://discussions.apple.com/thread.jspa?threadID=1683876

Viewテンプレートを使用してiPhoneプロジェクトを作成します。ビューコントローラーで、これを追加します:

- (void)viewDidLoad
{
    CGRect rect = CGRectMake(10, 10, 200, 100);
    MyView *myView = [[MyView alloc] initWithFrame:rect];
    [self.view addSubview:myView];
    [super viewDidLoad];
}

MyView単なるUIImageViewサブクラスです:

@interface MyView : UIImageView
{
}

これまでグラフィックコンテキストを使用したことはありませんでしたが、このコードを一緒に作成することに成功しました。2つのコーナーのコードがありません。コードを読むと、(CGContextAddArc呼び出しの一部を削除し、コードの半径値の一部を削除することで)これを実装した方法を確認できます。すべてのコーナーのコードがそこにあるので、それを開始点として使用して、不要なコーナーを作成するパーツです。必要に応じて、2つまたは3つの角が丸い長方形を作成することもできます。

コードは完璧ではありませんが、少し片付けることができると思います。

static void addRoundedRectToPath(CGContextRef context, CGRect rect, float radius, int roundedCornerPosition)
{

    // all corners rounded
    //  CGContextMoveToPoint(context, rect.origin.x, rect.origin.y + radius);
    //  CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height - radius);
    //  CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius, 
    //                  radius, M_PI / 4, M_PI / 2, 1);
    //  CGContextAddLineToPoint(context, rect.origin.x + rect.size.width - radius, 
    //                          rect.origin.y + rect.size.height);
    //  CGContextAddArc(context, rect.origin.x + rect.size.width - radius, 
    //                  rect.origin.y + rect.size.height - radius, radius, M_PI / 2, 0.0f, 1);
    //  CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y + radius);
    //  CGContextAddArc(context, rect.origin.x + rect.size.width - radius, rect.origin.y + radius, 
    //                  radius, 0.0f, -M_PI / 2, 1);
    //  CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y);
    //  CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + radius, radius, 
    //                  -M_PI / 2, M_PI, 1);

    // top left
    if (roundedCornerPosition == 1) {
        CGContextMoveToPoint(context, rect.origin.x, rect.origin.y + radius);
        CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height - radius);
        CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius, 
                        radius, M_PI / 4, M_PI / 2, 1);
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, 
                                rect.origin.y + rect.size.height);
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);
        CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y);
    }   

    // bottom left
    if (roundedCornerPosition == 2) {
        CGContextMoveToPoint(context, rect.origin.x, rect.origin.y);
        CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height);
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, 
                                rect.origin.y + rect.size.height);
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);
        CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y);
        CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + radius, radius, 
                        -M_PI / 2, M_PI, 1);
    }

    // add the other corners here


    CGContextClosePath(context);
    CGContextRestoreGState(context);
}


-(UIImage *)setImage
{
    UIImage *img = [UIImage imageNamed:@"my_image.png"];
    int w = img.size.width;
    int h = img.size.height;

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);

    CGContextBeginPath(context);
    CGRect rect = CGRectMake(0, 0, w, h);


    addRoundedRectToPath(context, rect, 50, 1);
    CGContextClosePath(context);
    CGContextClip(context);

    CGContextDrawImage(context, rect, img.CGImage);

    CGImageRef imageMasked = CGBitmapContextCreateImage(context);
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    [img release];

    return [UIImage imageWithCGImage:imageMasked];
}

代替テキストhttp://nevan.net/skitch/skitched-20100224-092237.png

これを機能させるには、QuartzCoreフレームワークをそこに取得する必要があることを忘れないでください。


サイズが変わる可能性のあるビューでは期待どおりに機能しません。例:UILabel。マスクパスを設定し、テキストを設定してサイズを変更すると、古いマスクが保持されます。drawRectのサブクラス化とオーバーロードが必要になります。
user3099609 2014年

358

iOS 3.2以降、UIBezierPaths の機能を使用して、すぐに使用できる丸みのある四角形を作成できます(指定した角だけが丸められます)。これをのパスCAShapeLayerとして使用し、ビューのレイヤーのマスクとして使用できます。

// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds 
                                               byRoundingCorners:UIRectCornerTopLeft
                                                     cornerRadii:CGSizeMake(10.0, 10.0)];

// Create the shape layer and set its path
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = imageView.bounds;
maskLayer.path = maskPath.CGPath;

// Set the newly created shape layer as the mask for the image view's layer
imageView.layer.mask = maskLayer;

それだけです。CoreGraphicsで手動で形状を定義したり、Photoshopでマスキングイメージを作成したりする必要はありません。レイヤーは無効にする必要さえありません。丸みを帯びたコーナーの適用または新しいコーナーへの変更は、新しいコーナーを定義し、UIBezierPathそれCGPathをマスクレイヤーのパスとして使用するのと同じくらい簡単です。メソッドのcornersパラメーターはbezierPathWithRoundedRect:byRoundingCorners:cornerRadii:ビットマスクであるため、複数のコーナーをOR演算することで丸めることができます。


編集:影を追加する

これに影を付けたい場合は、もう少し作業が必要です。

imageView.layer.mask = maskLayer」はマスクを適用するため、通常、シャドウはその外側に表示されません。秘訣は、透明なビューを使用してから、2つのサブレイヤー(CALayers)をビューのレイヤーに追加することです:shadowLayerroundedLayer。どちらもを使用する必要がありUIBezierPathます。画像がのコンテンツとして追加されroundedLayerます。

// Create a transparent view
UIView *theView = [[UIView alloc] initWithFrame:theFrame];
[theView setBackgroundColor:[UIColor clearColor]];

// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:theView.bounds 
                                               byRoundingCorners:UIRectCornerTopLeft
                                                     cornerRadii:CGSizeMake(10.0f, 10.0f)];

// Create the shadow layer
CAShapeLayer *shadowLayer = [CAShapeLayer layer];
[shadowLayer setFrame:theView.bounds];
[shadowLayer setMasksToBounds:NO];
[shadowLayer setShadowPath:maskPath.CGPath];
// ...
// Set the shadowColor, shadowOffset, shadowOpacity & shadowRadius as required
// ...

// Create the rounded layer, and mask it using the rounded mask layer
CALayer *roundedLayer = [CALayer layer];
[roundedLayer setFrame:theView.bounds];
[roundedLayer setContents:(id)theImage.CGImage];

CAShapeLayer *maskLayer = [CAShapeLayer layer];
[maskLayer setFrame:theView.bounds];
[maskLayer setPath:maskPath.CGPath];

roundedLayer.mask = maskLayer;

// Add these two layers as sublayers to the view
[theView.layer addSublayer:shadowLayer];
[theView.layer addSublayer:roundedLayer];

1
いいですね、これはグループ化されたUITableViewセルのselectedBackgroundViewsで多くの助けとなりました:-)
Luke47

とにかく、角を丸めた後、このビューにドロップシャドウを追加しますか?成功せずに次のことを試みました。`self.layer.masksToBounds = NO; self.layer.shadowColor = [UIColor blackColor] .CGColor; self.layer.shadowRadius = 3; self.layer.shadowOffset = CGSizeMake(-3、3); self.layer.shadowOpacity = 0.8; self.layer.shouldRasterize = YES; `
クリスワグナー

1
@ChrisWagner:丸みを帯びたビューにシャドウを適用することに関する私の編集を参照してください。
スチュアート

@StuDev素晴らしい!シャドウビューのサブビューを使用しましたが、これははるかに優れています。このようなサブレイヤーを追加することは考えていませんでした。ありがとう!
Chris Wagner、

画像が白くなるのはなぜですか。UITableViewをスクロールすると、セルが再作成されます-機能します!どうして?
フェルナンドレドンド

18

私はコードの多くの場所でこのコードを使用しており、100%正しく動作します。「byRoundingCorners:UIRectCornerBottomLeft」という1つのプロパティを変更することで、任意のコーダーを変更できます

UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:UIRectCornerBottomLeft cornerRadii:CGSizeMake(10.0, 10.0)];

                CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
                maskLayer.frame = view.bounds;
                maskLayer.path = maskPath.CGPath;
                view.layer.mask = maskLayer;
                [maskLayer release];

このソリューションをUITableViewサブビュー(例:UITableViewCellsまたはUITableViewHeaderFooterViews)で使用すると、スクロール時の滑らかさが低下し ます。その使用のための別のアプローチは、より良いパフォーマンスを備えたこのソリューションです(すべてのコーナーにcornerRadiusを追加します)。丸みを帯びた特定のコーナーのみを「表示」するために(右frame.origin.x上隅や右下隅など)、負の値を持つサブビューを追加し、cornerRadiusをそのレイヤーに割り当てました。誰かがより良い解決策を見つけた場合、私は興味があります。
anneblue 2014年

11

iOS 11では、一部のコーナーのみを丸めることができます

let view = UIView()

view.clipsToBounds = true
view.layer.cornerRadius = 8
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]

7

Swift 3+構文によるCALayer拡張

extension CALayer {

    func round(roundedRect rect: CGRect, byRoundingCorners corners: UIRectCorner, cornerRadii: CGSize) -> Void {
        let bp = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: cornerRadii)
        let sl = CAShapeLayer()
        sl.frame = self.bounds
        sl.path = bp.cgPath
        self.mask = sl
    }
}

次のように使用できます。

let layer: CALayer = yourView.layer
layer.round(roundedRect: yourView.bounds, byRoundingCorners: [.bottomLeft, .topLeft], cornerRadii: CGSize(width: 5, height: 5))

5

特定のコーナーを丸めるスチュアートの例はうまくいきます。左上や右上などの複数の角を丸めたい場合は、これを行う方法です

// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageview
                                               byRoundingCorners:UIRectCornerTopLeft|UIRectCornerTopRight
                                                     cornerRadii:CGSizeMake(10.0, 10.0)];

// Create the shape layer and set its path
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = imageview.bounds;
maskLayer.path = maskPath.CGPath;

// Set the newly created shape layer as the mask for the image view's layer
imageview.layer.mask = maskLayer; 

このソリューションをUITableViewサブビュー(例:UITableViewCellsまたはUITableViewHeaderFooterViews)で使用すると、スクロール時の滑らかさが低下し ます。その使用のための別のアプローチは、より優れたパフォーマンスを持つこのソリューションです(すべてのコーナーにcornerRadiusを追加します)。丸みを帯びた特定のコーナーのみを「表示」するには(右frame.origin.x上隅や右下隅など)、負の値を持つサブビューを追加して、cornerRadiusをそのレイヤーに割り当てました。誰かがより良い解決策を見つけた場合、私は興味があります。
anneblue 2014年

5

共有いただきありがとうございます。ここで、この問題の詳細を参照するために、swift 2.0での解決策を共有したいと思います。(UIRectCornerのプロトコルに準拠するため)

let mp = UIBezierPath(roundedRect: cell.bounds, byRoundingCorners: [.bottomLeft, .TopLeft], cornerRadii: CGSize(width: 10, height: 10))
let ml = CAShapeLayer()
ml.frame = self.bounds
ml.path = mp.CGPath
self.layer.mask = ml

4

必要に応じて機能し、影でも機能する、より簡単で高速な答えがあります。スーパーレイヤーのmaskToBoundsをtrueに設定し、2つの角がスーパーレイヤー境界の外側になるように子レイヤーをオフセットして、2つの側面の丸い角を効果的に切り取ることができます。

もちろん、これは、同じ側に丸い角を2つだけにしたい場合にのみ機能し、片側から数ピクセルを切り取ると、レイヤーのコンテンツは同じに見えます。棒グラフを上部のみで丸めるのに最適です。


3

この関連質問を参照してください。独自の長方形をいくつかの角の丸いに描画し、CGPathをに追加し、を使用してそこにクリップする必要があります。CGPathCGContextCGContextClip

アルファ値の丸められた四角形を画像に描画し、その画像を使用して、レイヤーのmaskプロパティとして設定する新しいレイヤーを作成することもできます(Appleのドキュメントを参照)。


2

半年遅れましたが、現在の人々のやり方は100%正しいとは思いません。UIBezierPath + CAShapeLayerメソッドを使用すると、特にストーリーボードに設定されている場合に、自動レイアウトが妨げられるという問題が多くの人にありました。これについては答えが出ないので、自分で追加することにしました。

これを回避する非常に簡単な方法がありdrawRect(rect: CGRect)ます。関数の丸い角を描画します。

たとえば、UIViewの角を丸くしたい場合は、UIViewをサブクラス化し、適切な場所でそのサブクラスを使用します。

import UIKit

class TopRoundedView: UIView {

    override func drawRect(rect: CGRect) {
        super.drawRect(rect)

        var maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: UIRectCorner.TopLeft | UIRectCorner.TopRight, cornerRadii: CGSizeMake(5.0, 5.0))

        var maskLayer = CAShapeLayer()
        maskLayer.frame = self.bounds
        maskLayer.path = maskPath.CGPath

        self.layer.mask = maskLayer
    }
}

これは問題を克服するための最良の方法であり、適応するのに少しの時間もかかりません。


1
これは問題を解決する正しい方法ではありません。drawRect(_:)(Core Graphicsを使用してなど)ビューでカスタム描画を行う場合にのみオーバーライドする必要があります。そうしないと、空の実装でさえパフォーマンスに影響を与える可能性があります。毎回新しいマスクレイヤーを作成する必要もありません。実際に行う必要があるのはpath、ビューの境界が変更されたときにマスクレイヤーのプロパティを更新することだけです。これを行う場所は、layoutSubviews()メソッドのオーバーライド内です。
スチュアート

2

一部のコーナーのみを丸めると、自動サイズ変更や自動レイアウトではうまく機能しません。

そのため、もう1つのオプションは、通常の方法を使用cornerRadiusして、不要なコーナーを別のビューの下またはスーパービューの境界の外に隠し、コンテンツをクリップするように設定されていることを確認することです。


1

追加するには答え加え、私は再利用可能なシンプル、作成したUIViewスウィフトでは。ユースケースによっては、変更を加えることもできます(すべてのレイアウトでオブジェクトを作成することなどは避けます)が、私はそれをできるだけ単純にしたかった。拡張機能を使用するとUIImageView、サブクラス化を好まない場合に、これを他のビュー(例)に簡単に適用できます。

extension UIView {

    func roundCorners(_ roundedCorners: UIRectCorner, toRadius radius: CGFloat) {
        roundCorners(roundedCorners, toRadii: CGSize(width: radius, height: radius))
    }

    func roundCorners(_ roundedCorners: UIRectCorner, toRadii cornerRadii: CGSize) {
        let maskBezierPath = UIBezierPath(
            roundedRect: bounds,
            byRoundingCorners: roundedCorners,
            cornerRadii: cornerRadii)
        let maskShapeLayer = CAShapeLayer()
        maskShapeLayer.frame = bounds
        maskShapeLayer.path = maskBezierPath.cgPath
        layer.mask = maskShapeLayer
    }
}

class RoundedCornerView: UIView {

    var roundedCorners: UIRectCorner = UIRectCorner.allCorners
    var roundedCornerRadii: CGSize = CGSize(width: 10.0, height: 10.0)

    override func layoutSubviews() {
        super.layoutSubviews()
        roundCorners(roundedCorners, toRadii: roundedCornerRadii)
    }
}

以下にそれを適用する方法を示しますUIViewController

class MyViewController: UIViewController {

    private var _view: RoundedCornerView {
        return view as! RoundedCornerView
    }

    override func loadView() {
        view = RoundedCornerView()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        _view.roundedCorners = [.topLeft, .topRight]
        _view.roundedCornerRadii = CGSize(width: 10.0, height: 10.0)
    }
}

0

スチュアートの答えをまとめると、次のように角を丸める方法があります。

@implementation UIView (RoundCorners)

- (void)applyRoundCorners:(UIRectCorner)corners radius:(CGFloat)radius {
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(radius, radius)];

    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.frame = self.bounds;
    maskLayer.path = maskPath.CGPath;

    self.layer.mask = maskLayer;
}

@end

したがって、角を丸めるには、次のようにします。

[self.imageView applyRoundCorners:UIRectCornerTopRight|UIRectCornerTopLeft radius:10];

0

レイヤーのマスクを定義することをお勧めします。マスク自体はCAShapeLayer、専用のパスを持つオブジェクトである必要があります。次のUIView拡張機能(Swift 4.2)を使用できます。

extension UIView {
    func round(corners: UIRectCorner, with radius: CGFloat) {
        let maskLayer = CAShapeLayer()
        maskLayer.frame = bounds
        maskLayer.path = UIBezierPath(
            roundedRect: bounds,
            byRoundingCorners: corners,
            cornerRadii: CGSize(width: radius, height: radius)
        ).cgPath
        layer.mask = maskLayer
   }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.