如何将视图边缘弯曲成弧形

Posted

技术标签:

【中文标题】如何将视图边缘弯曲成弧形【英文标题】:How to curve a view edge to looks like an arc 【发布时间】:2018-10-31 16:10:08 【问题描述】:

我想让视图看起来像这样:

如果有办法,比如用预定义的角度定义点 A、点 B。

我找到的唯一解决方案是制作一个巨大的圆形视图并将其插入为另一个带有clipToBounds = true 的视图的子视图。但这在多个屏幕尺寸上存在一些问题,因为我使用了约束。


Edit1:经过一番搜索,我尝试使用 CAShapeLayer 创建该视图,但没有成功。我正在通过情节提要创建该视图,并带有约束,该视图也由 IBOutlet 和您的主要约束连接。代码如下:

在 viewDidLoad 上:

self.cnstRoundedLeading.constant = -(self.vwRounded.frame.width/3)
let maskPath : UIBezierPath = UIBezierPath(roundedRect: CGRect(x: self.vwRounded.bounds.minX*4,
                                                                   y: self.vwRounded.bounds.minY*4,
                                                                   width: self.vwRounded.bounds.width*4,
                                                                   height: self.vwRounded.bounds.height*4),
                                                                    byRoundingCorners: .topLeft,
                                                                    cornerRadii: CGSize(width: self.vwRounded.frame.size.width*2,
                                                                                        height: self.vwRounded.frame.size.height))



let maskLayer : CAShapeLayer = CAShapeLayer()
    maskLayer.frame = self.vwRounded.bounds
    maskLayer.path = maskPath.cgPath
    self.vwRounded.layer.mask = maskLayer

在 viewWillLayoutSubviews 上:

gradient2.colors = [startColorBlue.cgColor, endColorBlue.cgColor]
gradient2.locations = [0.0, 1.0]
gradient2.startPoint = CGPoint(x: 0, y: 1)
gradient2.endPoint = CGPoint(x: 1, y: 0)
vwRounded.applyGradient(gradient2)

applyGradient 是 UIView 的扩展:

func applyGradient(_ gradient: CAGradientLayer) -> Void 
        gradient.frame = self.bounds
        self.layer.insertSublayer(gradient, at: 0)
    

不能正常工作,我不知道构建“弧边”效果的正确方法

【问题讨论】:

不清楚你在问什么。您想要一个看起来像红色四分之一圆的视图吗?这可以使用内容层是 CAShapeLayer 的视图来完成,如果这就是您所要求的。 不完全是四分之一圆,但几乎是这个。圆的一小部分,我不确定圆的哪些部分是那个部分。 @RM - 显示您用于创建该弧/形状的代码... @DonMag - 我不知道如何创建它,我正在做的是在情节提要上创建一个屏幕大小约为 3 倍的视图,并添加 self.vwRounded.layer.cornerRadius = self.vwRounded.frame.size.width/2 但这样做,我我在不同的屏幕尺寸上遇到了一些问题 @RM - 你想显示这个形状吗?或者您想用这个形状屏蔽视图(例如图像视图?) 【参考方案1】:

您可以使用UIBezierPath(黑色边框显示实际视图框架)创建该形状 - 并将其用作蒙版:

基本上,

找到从pt1pt2 的直线的中点。 找到垂直于该线的点,该点与该线的距离使曲线看起来像您想要的那样。此处显示使用与半行长度相同的长度。 创建一个UIBezierPath,用二次曲线将pt1 连接到pt2

这是您可以直接在 Playground 页面中运行的示例代码。我根据您发布的图像基于pt1pt2 y 位置...如果您更改视图的框架,它将保持您显示的比例。

import PlaygroundSupport
import UIKit

class TestViewController: UIViewController 

    override public var preferredContentSize: CGSize 
        get  return CGSize(width: 800, height: 800) 
        set  super.preferredContentSize = newValue 
    

    let myPlainView: UIView = 
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    ()

    let myBorderView: UIView = 
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    ()

    func customCurvedPath(for rect: CGRect) -> UIBezierPath 

        // curve start point Y is 490/544ths of the height of the view
        let curveStartPoint = CGPoint(x: 0.0, y: rect.size.height * 490.0 / 544.0)

        // curve end point Y is 22/544ths of the height of the view
        let curveEndPoint = CGPoint(x: rect.size.width, y: rect.size.height * 22.0 / 544.0)

        var x1 = curveStartPoint.x
        var y1 = curveStartPoint.y

        let x2 = curveEndPoint.x
        let y2 = curveEndPoint.y

        // get the midpoint of the line from x1,y1 to x2,y2
        x1 = (x1 + x2) / 2.0
        y1 = (y1 + y2) / 2.0

        // get the length of half the line (midpoint to endpoint)
        var dx = x1 - x2
        var dy = y1 - y2
        let dist = sqrt(dx*dx + dy*dy)

        // use length of helf the line for distance from line
        // increase or decrease this value to get the desired curve
        let distFromLine = dist

        dx /= dist
        dy /= dist

        // get perpendicular point at distFromLine
        let x3 = x1 - (distFromLine/2)*dy
        let y3 = y1 + (distFromLine/2)*dx

        let curveControlPoint = CGPoint(x: x3, y: y3)

        let myBezier = UIBezierPath()

        // pt1
        myBezier.move(to: curveStartPoint)

        // quad curve to pt2
        myBezier.addQuadCurve(to: curveEndPoint, controlPoint: curveControlPoint)

        // line to pt3 (bottom right corner)
        myBezier.addLine(to: CGPoint(x: rect.width, y: rect.height))

        // line to pt4 (bottom left corner)
        myBezier.addLine(to: CGPoint(x: 0.0, y: rect.height))

        // close the path (automatically add a line from bottom left corner to curve start point)
        myBezier.close()

        return myBezier

    

    override func viewDidLoad() 
        super.viewDidLoad()

        view.backgroundColor = .white

        let vwWidth = CGFloat(710.0)
        let vwHeight = CGFloat(544.0)

        view.addSubview(myBorderView)

        myBorderView.backgroundColor = .clear

        NSLayoutConstraint.activate([

            myBorderView.widthAnchor.constraint(equalToConstant: vwWidth + 2.0),
            myBorderView.heightAnchor.constraint(equalToConstant: vwHeight + 2.0),
            myBorderView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            myBorderView.centerYAnchor.constraint(equalTo: view.centerYAnchor),

            ])

        myBorderView.layer.borderWidth = 2.0

        // comment this next line (or set to false) to see the actual view frame
        myBorderView.isHidden = true

        view.addSubview(myPlainView)

        myPlainView.backgroundColor = .red

        NSLayoutConstraint.activate([

            myPlainView.widthAnchor.constraint(equalToConstant: vwWidth),
            myPlainView.heightAnchor.constraint(equalToConstant: vwHeight),
            myPlainView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            myPlainView.centerYAnchor.constraint(equalTo: view.centerYAnchor),

            ])

        let bezPath = customCurvedPath(for: CGRect(x: 0, y: 0, width: vwWidth, height: vwHeight))

        // add the bezier path as a layer mask
        let maskForPath = CAShapeLayer()
        maskForPath.path = bezPath.cgPath
        myPlainView.layer.mask = maskForPath

    



let viewController = TestViewController()

PlaygroundPage.current.liveView = viewController

正如我所提到的,这作为自定义视图类的一部分会更好,因为您可以覆盖 layoutSubviews() 以保持路径形状一致。

这是一个在自定义视图中使用渐变图层+图层蒙版的示例,设置为300 x 250

还有 Playground 可运行源:

import PlaygroundSupport
import UIKit

class MaskedGradientView: UIView 

    var gradLayer: CAGradientLayer!

    override init(frame: CGRect) 
        super.init(frame: frame)
        commonInit()
    

    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
        commonInit()
    

    func commonInit() -> Void 
        gradLayer = CAGradientLayer()
        gradLayer.colors = [UIColor.blue.cgColor, UIColor.cyan.cgColor]
        gradLayer.locations = [0.0, 1.0]
        gradLayer.startPoint = CGPoint(x: 0, y: 1)
        gradLayer.endPoint = CGPoint(x: 1, y: 0)
        layer.addSublayer(gradLayer)
    

    override func layoutSubviews() 
        super.layoutSubviews()

        let rect = self.bounds

        gradLayer.frame = self.bounds

        // curve start point Y is 490/544ths of the height of the view
        let curveStartPoint = CGPoint(x: 0.0, y: rect.size.height * 490.0 / 544.0)

        // curve end point Y is 22/544ths of the height of the view
        let curveEndPoint = CGPoint(x: rect.size.width, y: rect.size.height * 22.0 / 544.0)

        var x1 = curveStartPoint.x
        var y1 = curveStartPoint.y

        let x2 = curveEndPoint.x
        let y2 = curveEndPoint.y

        // get the midpoint of the line from x1,y1 to x2,y2
        x1 = (x1 + x2) / 2.0
        y1 = (y1 + y2) / 2.0

        // get the length of half the line (midpoint to endpoint)
        var dx = x1 - x2
        var dy = y1 - y2
        let dist = sqrt(dx*dx + dy*dy)

        // use length of helf the line for distance from line
        // increase or decrease this value to get the desired curve
        let distFromLine = dist

        dx /= dist
        dy /= dist

        // get perpendicular point at distFromLine
        let x3 = x1 - (distFromLine/2)*dy
        let y3 = y1 + (distFromLine/2)*dx

        let curveControlPoint = CGPoint(x: x3, y: y3)

        let myBezier = UIBezierPath()

        // pt1
        myBezier.move(to: curveStartPoint)

        // quad curve to pt2
        myBezier.addQuadCurve(to: curveEndPoint, controlPoint: curveControlPoint)

        // line to pt3 (bottom right corner)
        myBezier.addLine(to: CGPoint(x: rect.width, y: rect.height))

        // line to pt4 (bottom left corner)
        myBezier.addLine(to: CGPoint(x: 0.0, y: rect.height))

        // close the path (automatically add a line from bottom left corner to curve start point)
        myBezier.close()

        // add the bezier path as a layer mask
        let maskForPath = CAShapeLayer()
        maskForPath.path = myBezier.cgPath
        layer.mask = maskForPath

    



class TestViewController: UIViewController 

    override public var preferredContentSize: CGSize 
        get  return CGSize(width: 400, height: 400) 
        set  super.preferredContentSize = newValue 
    

    let myMaskedGradientView: MaskedGradientView = 
        let v = MaskedGradientView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    ()

    let myPlainView: UIView = 
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .blue
        return v
    ()

    override func viewDidLoad() 
        super.viewDidLoad()

        view.backgroundColor = .white

        view.addSubview(myMaskedGradientView)

        NSLayoutConstraint.activate([

            myMaskedGradientView.widthAnchor.constraint(equalToConstant: 300.0),
            myMaskedGradientView.heightAnchor.constraint(equalToConstant: 250.0),
            myMaskedGradientView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            myMaskedGradientView.centerYAnchor.constraint(equalTo: view.centerYAnchor),

            ])

    



let viewController = TestViewController()

PlaygroundPage.current.liveView = viewController

【讨论】:

太棒了,这正是我想要的,一些小改动以适应我的代码,并且运行良好。非常感谢。

以上是关于如何将视图边缘弯曲成弧形的主要内容,如果未能解决你的问题,请参考以下文章

是否可以创建具有弯曲底部边缘的 UIView。

在弧形android的边缘添加一个圆圈?

如何在 iOS 中绘制带有弯曲边缘的弧?

如何使用 Python 在一组点上绘制多边形(部分向内弯曲)边缘?

带有波纹动画的Android自定义视图边缘裁剪

带有弯曲边缘的 Android 矩形