在Swift中如何创建只有两个圆角的矩形?

55

我需要在Swift中创建一个只有两个圆角的矩形(也可以使用Objective C代码)。

目前我的代码正在创建两个矩形

CGPathCreateWithRoundedRect(CGRectMake(0, 0, 30, 60), 5, 5, nil);

并且

CGPathCreateWithRoundedRect(CGRectMake(0, 0, 30, 60), 0, 0, nil);

我想把它们合并在一起(拥有两个直角和两个圆角),但我对代码不满意,我相信应该有更好的方法来完成它。

我是iOS和图形开发以及Swift方面的新手。


1
bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:该方法是 UIBezierPath 类的一个类方法,用于创建一个带有圆角的矩形路径。它需要四个参数:矩形的大小、要圆角的角落、以及每个角落的半径。 - matt
1
通常在Swift中有许多小的更改,例如常量的大小写等。建议向下滚动到最新答案。 - Fattie
请查看我的答案,它会涵盖所有内容:https://dev59.com/t2kw5IYBdhLWcg3wDWTL#68342661 - Vipul Kumar
16个回答

69

更新:请参见下面链接的这个回答,适用于Swift 4 / iOS 11,要简单得多。


以下是一个快速的Swift 3扩展,可用于四舍五入和可选边框。

注意:如果您正在使用自动布局,您可能需要在其中一个视图生命周期回调中调用此函数,如viewDidLayoutSubviewslayoutSubviews,在视图已被约束后。

import UIKit

extension UIView {
    
    /**
     Rounds the given set of corners to the specified radius
     
     - parameter corners: Corners to round
     - parameter radius:  Radius to round to
     */
    func round(corners: UIRectCorner, radius: CGFloat) {
        _ = _round(corners: corners, radius: radius)
    }
    
    /**
     Rounds the given set of corners to the specified radius with a border
     
     - parameter corners:     Corners to round
     - parameter radius:      Radius to round to
     - parameter borderColor: The border color
     - parameter borderWidth: The border width
     */
    func round(corners: UIRectCorner, radius: CGFloat, borderColor: UIColor, borderWidth: CGFloat) {
        let mask = _round(corners: corners, radius: radius)
        addBorder(mask: mask, borderColor: borderColor, borderWidth: borderWidth)
    }
    
    /**
     Fully rounds an autolayout view (e.g. one with no known frame) with the given diameter and border
     
     - parameter diameter:    The view's diameter
     - parameter borderColor: The border color
     - parameter borderWidth: The border width
     */
    func fullyRound(diameter: CGFloat, borderColor: UIColor, borderWidth: CGFloat) {
        layer.masksToBounds = true
        layer.cornerRadius = diameter / 2
        layer.borderWidth = borderWidth
        layer.borderColor = borderColor.cgColor;
    }
    
}

private extension UIView {
    
    @discardableResult func _round(corners: UIRectCorner, radius: CGFloat) -> CAShapeLayer {
        let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        self.layer.mask = mask
        return mask
    }
    
    func addBorder(mask: CAShapeLayer, borderColor: UIColor, borderWidth: CGFloat) {
        let borderLayer = CAShapeLayer()
        borderLayer.path = mask.path
        borderLayer.fillColor = UIColor.clear.cgColor
        borderLayer.strokeColor = borderColor.cgColor
        borderLayer.lineWidth = borderWidth
        borderLayer.frame = bounds
        layer.addSublayer(borderLayer)
    }
    
}

5
更新:刚刚必须在 viewDidLayoutSubviews 中调用扩展。 - Onichan
3
addBorder(mask:borderColor:borderWidth:) 方法需要小心使用,因为它总是添加一个新的层。如果在一个空的 UIView 中使用 round(corners:radius:borderColor:borderWidth:) 方法并且 viewDidLayoutSubviewslayoutSubviews 被调用了 5 次……那么该视图将有 5 个子层! - ricardopereira
请注意,lineWidth 不会检查屏幕比例,因此 borderWidth 应该乘以 UIScreen.main.scale - ricardopereira
当我的视图大小是动态的时候,如何使用它?比如多行标签。我应该在什么时候调用这个函数来进行四舍五入? - Bibek
@Dari请参考上文:在 viewDidLayoutSubviews 生命周期回调中。 - iwasrobbed
显示剩余8条评论

69

Swift 4+,iOS 11+

如果您已经有一个名为myViewUIView作为IBOutlet引用,请尝试在ViewDidLoad()或加载它的任何地方添加以下两行代码:

myView.layer.cornerRadius = 10
myView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]

您可以更改数组[]以选择所需的角落组合,包括MinXMinYMaxXMaxY。上面的示例将底部两个角落进行四舍五入。

这只是另一种方法,根据您的设计可能会更简单。


2
重要提示:此解决方案仅适用于iOS 11及以上版本。 - monolith
1
对于顶部的两个角,您可以使用 view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]。 - Amit Thakur
非常好的,谢谢。虽然像layerMinXMinYCorner这样的命名在技术上是正确的,但我想知道为什么Swift有时会那么丑陋。 - brainray

58

Swift 2.3 中,您可以通过以下方式实现:

let maskPath = UIBezierPath(roundedRect: anyView.bounds,
            byRoundingCorners: [.BottomLeft, .BottomRight],
            cornerRadii: CGSize(width: 10.0, height: 10.0))

let shape = CAShapeLayer()
shape.path = maskPath.CGPath
view.layer.mask = shape
在Objective-C中,您可以使用UIBezierPath类方法。
bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:

示例实现 -

// set the corner radius to the specified corners of the passed container
- (void)setMaskTo:(UIView*)view byRoundingCorners:(UIRectCorner)corners
{
    UIBezierPath *rounded = [UIBezierPath bezierPathWithRoundedRect:view.bounds
                                                  byRoundingCorners:corners
                                                        cornerRadii:CGSizeMake(10.0, 10.0)];
    CAShapeLayer *shape = [[CAShapeLayer alloc] init];
    [shape setPath:rounded.CGPath];
    view.layer.mask = shape;
}

并调用上述方法为-

[self setMaskTo:anyView byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight];

#define RADIANS(degrees) ((degrees) / (180.0 / M_PI)) 它是一个用于将角度转换为弧度的C语言宏定义。 - Sanjay Mohnani
double startAngle = RADIANS(45); double endAngle = RADIANS(135); 双精度型的起始角度为弧度制下的45度; 双精度型的结束角度为弧度制下的135度; - Sanjay Mohnani
完美,它在Swift中确实可用,就像Objective-C中的#define。 - Sanjay Mohnani
你怎么在Swift 2.0中实现这个? "|" 似乎不能用。 - Andy
3
不为底部工作,即底部左侧和底部右侧。 - user1673099
显示剩余4条评论

26

Swift 3 - 当您需要将特定视图的某些角落变为圆角时,以下是一个有用的UIView扩展:


extension UIView {
  func round(corners: UIRectCorner, radius: CGFloat) {
    let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
    let mask = CAShapeLayer()
    mask.path = path.cgPath
    self.layer.mask = mask
  }
}

那么就像这样使用它:

someView.round(corners: [.topLeft, .topRight], radius: 5)

如何为此视图设置边框或阴影? - Vadlapalli Masthan
@VadlapalliMasthan,你可以像平常一样操作。只需确保在圆角、阴影和边框应用之前设置视图的框架即可。 - budiDino

11

在Sanjay出色的回答基础上,我编写了一个快速的CALayer扩展程序,适用于Swift 2.3,以防您需要多次执行“仅圆角化某些角落”的操作。

extension CALayer {
  func roundCorners(corners: UIRectCorner, radius: CGFloat) {
    let maskPath = UIBezierPath(roundedRect: bounds,
                                byRoundingCorners: corners,
                                cornerRadii: CGSize(width: radius, height: radius))

    let shape = CAShapeLayer()
    shape.path = maskPath.CGPath
    mask = shape
  }
}

使用方法:

myView.layer.roundCorners([.TopLeft, .TopRight], radius: myCornerRadius)

Swift 3.0(在此示例中,边界来自于视图而非图层。使用视图的边界可以使这段代码适用于UITableViewCell中的视图。):

func roundCorners(corners: UIRectCorner, radius: CGFloat, viewBounds: CGRect) {

    let maskPath = UIBezierPath(roundedRect: viewBounds,
                                byRoundingCorners: corners,
                                cornerRadii: CGSize(width: radius, height: radius))

    let shape = CAShapeLayer()
    shape.path = maskPath.cgPath
    mask = shape
}

用法:

myView.layer.roundCorners(corners: [.topLeft, .topRight], radius: myCornerRadius, viewBounds: bounds)

好的 - 像往常一样,Swift 有许多小的变化,例如常量的大小写等。 - Fattie
有时候,当我将顶部角落变成圆形时,图片不会显示出来,所以我需要在这一行之前添加myView.layoutIfNeeded()。 - satoukum
嗨@satoukum - 现代Xcode中处理这个问题的正确方法在我的下面的答案中展示,祝好运。 - Fattie

9

2021年最新版本...

请注意,自很久以前提出这个问题以来,语法/系统已经发生了很大变化!

在此输入图片描述

import UIKit
@IBDesignable
class RoundedEnds: UIView {
    
    override func layoutSubviews() {
        super.layoutSubviews()
        setup()
    }
    
    func setup() {
        let r = self.bounds.size.height / 2
        let path = UIBezierPath(roundedRect: self.bounds, cornerRadius:r)
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        self.layer.mask = mask
    }
}

只想调整某些角落?只需将path代码行更改为:

enter image description here

    let path = UIBezierPath(
        roundedRect: self.bounds,
        byRoundingCorners: [.topLeft,.topRight],
        cornerRadii: CGSize(width: r, height: r))

9
iOS 11+以上 | 您可以在此处查看iOS使用情况统计:https://developer.apple.com/support/app-store/

解释

由于CACornerMask rawValue是一个UInt,因此您知道CACornerMask rawValue是每个CACornerMask.Element rawValue的总和。

更具体地说:

  • 左上角(layerMinXMinYCorner)= 1
  • 右上角(layerMaxXMinYCorner)= 2
  • 左下角(layerMinXMaxYCorner)= 4
  • 右下角(layerMaxXMaxYCorner)= 8

因此,例如,如果您想要左上角右上角,只需键入CACornerMask(rawValue: 3)


 示例

以下是UIView的简单扩展

extension UIView {
    enum Corner:Int {
        case bottomRight = 0,
        topRight,
        bottomLeft,
        topLeft
    }
    
    private func parseCorner(corner: Corner) -> CACornerMask.Element {
        let corners: [CACornerMask.Element] = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]
        return corners[corner.rawValue]
    }
    
    private func createMask(corners: [Corner]) -> UInt {
        return corners.reduce(0, { (a, b) -> UInt in
            return a + parseCorner(corner: b).rawValue
        })
    }
    
    func roundCorners(corners: [Corner], amount: CGFloat = 5) {
        layer.cornerRadius = amount
        let maskedCorners: CACornerMask = CACornerMask(rawValue: createMask(corners: corners))
        layer.maskedCorners = maskedCorners
    }
}

您可以像这样使用它:
let myRect = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
myRect.roundCorners(corners: [.topRight, .topLeft])

8

以下是在Swift 2.0中的操作步骤:

var maskPath = UIBezierPath(roundedRect: anyView.bounds,
        byRoundingCorners: [.BottomLeft, .BottomRight],
        cornerRadii: CGSize(width: 10.0, height: 10.0))

不适用于底部。即 bottomLeft 和 bottomRight。 - user1673099

4

Swift 4:

let maskPath = UIBezierPath(
            roundedRect: view.bounds,
            byRoundingCorners: [.allCorners],
            cornerRadii: CGSize(width: 10.0, height: 10.0)
        )

let shape = CAShapeLayer()
shape.path = maskPath.cgPath

view.layer.mask = shape

如果我只想要四个角中的三个怎么办?[.bottomLeft, .bottomRight, .topRight] 无法使用。 - Piotr Wasilewicz

3

更新iWasRobbed的答案,以适应Swift 3.0 GM版本:

import UIKit

extension UIView {

    /**
     Rounds the given set of corners to the specified radius

     - parameter corners: Corners to round
     - parameter radius:  Radius to round to
     */
    func round(corners: UIRectCorner, radius: CGFloat) {
        _round(corners: corners, radius: radius)
    }

    /**
     Rounds the given set of corners to the specified radius with a border

     - parameter corners:     Corners to round
     - parameter radius:      Radius to round to
     - parameter borderColor: The border color
     - parameter borderWidth: The border width
     */
    func round(corners: UIRectCorner, radius: CGFloat, borderColor: UIColor, borderWidth: CGFloat) {
        let mask = _round(corners: corners, radius: radius)
        addBorder(mask: mask, borderColor: borderColor, borderWidth: borderWidth)
    }

    /**
     Fully rounds an autolayout view (e.g. one with no known frame) with the given diameter and border

     - parameter diameter:    The view's diameter
     - parameter borderColor: The border color
     - parameter borderWidth: The border width
     */
    func fullyRound(diameter: CGFloat, borderColor: UIColor, borderWidth: CGFloat) {
        layer.masksToBounds = true
        layer.cornerRadius = diameter / 2
        layer.borderWidth = borderWidth
        layer.borderColor = borderColor.cgColor;
    }

}

private extension UIView {

    @discardableResult func _round(corners: UIRectCorner, radius: CGFloat) -> CAShapeLayer {
        let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        self.layer.mask = mask
        return mask
    }

    func addBorder(mask: CAShapeLayer, borderColor: UIColor, borderWidth: CGFloat) {
        let borderLayer = CAShapeLayer()
        borderLayer.path = mask.path
        borderLayer.fillColor = UIColor.clear.cgColor
        borderLayer.strokeColor = borderColor.cgColor
        borderLayer.lineWidth = borderWidth
        borderLayer.frame = bounds
        layer.addSublayer(borderLayer)
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接