CALayer kullanarak yuvarlatılmış UIView - yalnızca bazı köşeler - Nasıl?


121

Uygulamamda - aşağıdaki gibi adlandırılan dört düğme var:

  • Sol üst
  • Sol alt
  • Sağ üst
  • Sağ alt

Düğmelerin üzerinde bir resim görünümü (veya bir UIView) vardır.

Şimdi, bir kullanıcının sol üstteki düğmeye dokunduğunu varsayalım. Yukarıdaki resim / görünüm o belirli köşede yuvarlatılmalıdır.

UIView'a yuvarlatılmış köşeler uygulamakta bazı zorluklar yaşıyorum.

Şu anda her görünüme yuvarlatılmış köşeleri uygulamak için aşağıdaki kodu kullanıyorum:

    // 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]];

Yukarıdaki kod, sağlanan Görünümün her bir köşesine yuvarlaklığı uygulamaktır. Bunun yerine - üst / üst + sol / alt + sağ vb.

Mümkün mü? Nasıl?

Yanıtlar:


44

Cevabı iPhone'da nasıl yuvarlak köşeli UILabel oluşturabilirim? ve kod iphone'da şeffaflıkla yuvarlak bir düz görünüm nasıl yapılır? bu kodu yapmak için.

Sonra yanlış soruyu yanıtladığımı fark ettim (UIImage yerine yuvarlak bir UILabel verdim) ve bu kodu değiştirmek için kullandım:

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

Görünüm şablonuyla bir iPhone projesi oluşturun. Görünüm denetleyicisinde şunu ekleyin:

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

MyViewSadece bir UIImageViewalt sınıf:

@interface MyView : UIImageView
{
}

Daha önce hiç grafik bağlamı kullanmamıştım, ancak bu kodu bir araya getirmeyi başardım. İki köşenin kodu eksik. Kodu okursanız, bunu nasıl uyguladığımı görebilirsiniz (bazı CGContextAddArcçağrıları silerek ve koddaki bazı yarıçap değerlerini silerek. Tüm köşeler için kod oradadır, bu nedenle bunu bir başlangıç ​​noktası olarak kullanın ve İhtiyaç duymadığınız köşeleri oluşturan parçalar. İsterseniz 2 veya 3 köşeli dikdörtgenler de yapabileceğinizi unutmayın.

Kod mükemmel değil, ama eminim biraz düzeltebilirsin.

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];
}

alternatif metin http://nevan.net/skitch/skitched-20100224-092237.png

Bunun çalışması için QuartzCore çerçevesini oraya almanız gerekeceğini unutmayın.


Boyutları değişebilecek görünümlerde beklendiği gibi çalışmaz. Örnek: bir UILabel. Maske yolunu ayarlamak ve ardından metni ona ayarlayarak boyutu değiştirmek eski maskeyi koruyacaktır. DrawRect için alt sınıflandırma ve aşırı yükleme gerekecektir.
user3099609

358

İOS 3.2'den başlayarak, kullanıma UIBezierPathhazır yuvarlatılmış bir dikdörtgen oluşturmak için s'nin işlevselliğini kullanabilirsiniz (burada yalnızca belirttiğiniz köşeler yuvarlanır). Daha sonra bunu a'nın yolu olarak CAShapeLayerkullanabilir ve bunu görünüm katmanı için bir maske olarak kullanabilirsiniz:

// 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;

İşte bu kadar - Core Graphics'te şekilleri manuel olarak tanımlamakla uğraşmak, Photoshop'ta maskeleme görüntüleri oluşturmak yok. Katmanın geçersiz kılınmasına bile gerek yok. Yuvarlatılmış köşeyi uygulamak veya yeni bir köşeye geçmek, yeni bir köşeyi tanımlamak UIBezierPathve CGPathbunu maske katmanının yolu olarak kullanmak kadar basittir . cornersParametre bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:yöntemi bir bit maskesi ve bu yüzden birden çok köşe birbirine VEYA işlemi yuvarlanabilir.


DÜZENLEME: Bir gölge ekleme

Buna bir gölge eklemek istiyorsanız, biraz daha çalışma yapılması gerekiyor.

" imageView.layer.mask = maskLayer" Bir maske uyguladığından, normalde dışında bir gölge görünmez. İşin püf noktası, şeffaf bir görünüm kullanmak ve ardından görünümün katmanına iki alt katman eklemektir CALayer: shadowLayerve roundedLayer. Her ikisinin de UIBezierPath. Görüntü, içeriği olarak eklenir 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
Güzel biri, bu gruplanmış UITableView hücresinin seçili
Arka Plan Görünümleri

Her neyse, köşeleri yuvarladıktan sonra bu görünüme gölge eklemek ister misiniz? Aşağıdakileri başarılı olmadan denedi. `self.layer.masksToBounds = HAYIR; 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; `
Chris Wagner

1
@ChrisWagner: Yuvarlak görünüme gölge uygulama konusunda düzenlememe bakın.
Stuart

@StuDev mükemmel! Gölge görünümü için bir alt görünümle gitmiştim ama bu çok daha güzel! Bunun gibi alt katmanlar eklemeyi düşünmemiştim. Teşekkürler!
Chris Wagner

Resmim neden beyaz oluyor, sonra UITableView'ü kaydırdığımda ve hücre yeniden oluşturulduğunda - işe yarıyor! Neden?
Fernando Redondo

18

Bu kodu kodumun birçok yerinde kullandım ve% 100 doğru çalışıyor. "ByRoundingCorners: UIRectCornerBottomLeft" tek bir özelliği değiştirerek herhangi bir corder'ı değiştirebilirsiniz.

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];

Bu çözümü alt UITableViewgörünümlerde (örn. UITableViewCellS veya UITableViewHeaderFooterViews) kullanırken, kaydırırken kötü pürüzsüzlükle sonuçlanır . Bu kullanım için başka bir yaklaşım daha iyi performansa sahip bu çözümdür (tüm köşelere bir cornerRadius ekler). Yalnızca yuvarlatılmış belirli köşeleri 'göstermek' için (üst ve sağ alt köşeler gibi) üzerinde negatif bir değer olan bir alt frame.origin.xgörünüm ekledim ve katmanına cornerRadius'u atadım. Birisi daha iyi bir çözüm bulursa, ilgilenirim.
anneblue

11

İOS 11'de artık yalnızca bazı köşeleri yuvarlayabiliyoruz

let view = UIView()

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

8

Swift 3+ sözdizimine sahip CALayer uzantısı

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
    }
}

Şu şekilde kullanılabilir:

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

5

Belirli köşeleri yuvarlamak için Stuarts örneği harika çalışıyor. Sol ve sağ üst gibi birden fazla köşeyi yuvarlamak istiyorsanız, bunu nasıl yapacağınız budur.

// 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; 

Bu çözümü alt UITableViewgörünümlerde (örn. UITableViewCellS veya UITableViewHeaderFooterViews) kullanırken, kaydırırken kötü pürüzsüzlükle sonuçlanır . Bu kullanım için başka bir yaklaşım daha iyi performansa sahip bu çözümdür (tüm köşelere bir cornerRadius ekler). Yalnızca yuvarlatılmış belirli köşeleri 'göstermek' için (üst ve sağ alt köşeler gibi) üzerinde negatif bir değer olan bir alt frame.origin.xgörünüm ekledim ve katmanına cornerRadius'u atadım. Birisi daha iyi bir çözüm bulursa, ilgilenirim.
anneblue

5

Paylaşım için teşekkürler. Bu konuyla ilgili daha fazla referans için burada çözümü hızlı 2.0'da paylaşmak istiyorum. (UIRectCorner protokolüne uymak için)

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

İhtiyaçlarınıza bağlı olarak işe yarayabilecek ve gölgelerle de çalışan daha kolay ve hızlı bir cevap var. Süper oyuncudaki maskToBounds'u true olarak ayarlayabilir ve alt katmanları, köşelerinden 2'si süper katman sınırlarının dışında olacak şekilde dengeleyebilir, böylece 2 taraftaki yuvarlatılmış köşeleri etkili bir şekilde kesebilirsiniz.

elbette bu sadece aynı tarafta sadece 2 yuvarlak köşeye sahip olmak istediğinizde ve bir taraftan birkaç pikseli kestiğinizde katmanın içeriği aynı göründüğünde işe yarar. yalnızca üst tarafta yuvarlatılmış çubuk grafiklere sahip olmak için harika çalışıyor.


3

Bu ilgili soruya bakın . Kendi dikdörtgeni bazı yuvarlatılmış köşeleri CGPatholan bir a'ya çizmen, CGPathsenin için eklemen CGContextve ardından kullanarak ona kırpman gerekecek CGContextClip.

Ayrıca bir görüntüye yuvarlatılmış rektifi alfa değerleriyle çizebilir ve ardından bu görüntüyü, katmanınızın masközelliği olarak ayarladığınız yeni bir katman oluşturmak için kullanabilirsiniz ( Apple'ın belgelerine bakın ).


2

Yarım on yıl geç, ama bence insanların bunu yapma şekli% 100 doğru değil. Birçok kişi, UIBezierPath + CAShapeLayer yöntemini kullanmanın, özellikle Storyboard'da ayarlandığında Otomatik düzen ile çakışması sorununu yaşadı. Bunun üzerinden geçecek cevap yok, ben de kendi yanıtımı eklemeye karar verdim.

Bunu aşmanın çok kolay bir yolu var: İşlevde yuvarlatılmış köşeleri çizin drawRect(rect: CGRect).

Örneğin, bir UIView için en üst yuvarlatılmış köşeleri isteseydim, UIView alt sınıfına girer ve uygun olan yerlerde bu alt sınıfı kullanırdım.

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
    }
}

Bu, sorunun üstesinden gelmenin en iyi yoludur ve uyum sağlamak için hiç zaman almaz.


1
Sorunu çözmenin doğru yolu bu değil. Yalnızca drawRect(_:)görünümünüzde özel çizim yapıyorsanız (örn. Core Graphics ile) geçersiz kılmalısınız , aksi takdirde boş bir uygulama bile performansı etkileyebilir. Her seferinde yeni bir maske katmanı oluşturmak da gereksizdir. Gerçekte yapmanız gereken tek şey, pathgörünümün sınırları değiştiğinde maske katmanının özelliğini güncellemektir ve bunun yapılacağı yer, layoutSubviews()yöntemin geçersiz kılınması dahilindedir .
Stuart

2

Yalnızca bazı köşeleri yuvarlamak, otomatik yeniden boyutlandırma veya otomatik düzen ile iyi oynamayacaktır.

Bu nedenle başka bir seçenek, normal kullanmak cornerRadiusve başka bir görünüm altında veya gözetmen görüntüleme sınırlarının dışında istemediğiniz köşeleri gizleyerek içeriğini kırpacak şekilde ayarlandığından emin olmaktır.


1

İçin eklemek için cevap ve ek , ben yeniden kullanılabilir bir basit yarattı UIViewSwift. Kullanım durumunuza bağlı olarak, değişiklikler yapmak isteyebilirsiniz (her düzende nesne oluşturmaktan kaçının vb.), Ancak bunu olabildiğince basit tutmak istedim. Uzantı, UIImageViewalt sınıflamayı sevmiyorsanız, bunu diğer görünümlere (örneğin ) daha kolay uygulamanıza izin verir .

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)
    }
}

Bunu a'ya nasıl uygulayacağınız aşağıda açıklanmıştır 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

Stuart'ın cevabını özetleyerek, aşağıdaki gibi yuvarlatılmış köşe yöntemine sahip olabilirsiniz:

@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

Yani yuvarlak köşeyi uygulamak için yapmanız gereken:

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

0

Bir katman maskesi tanımlamayı öneririm. Maskenin kendisi, kendisine CAShapeLayerayrılmış yolu olan bir nesne olmalıdır . Bir sonraki UIView uzantısını (Swift 4.2) kullanabilirsiniz:

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
   }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.