如何将视图边缘弯曲成弧形
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
(黑色边框显示实际视图框架)创建该形状 - 并将其用作蒙版:
基本上,
找到从pt1
到pt2
的直线的中点。
找到垂直于该线的点,该点与该线的距离使曲线看起来像您想要的那样。此处显示使用与半行长度相同的长度。
创建一个UIBezierPath
,用二次曲线将pt1
连接到pt2
。
这是您可以直接在 Playground 页面中运行的示例代码。我根据您发布的图像基于pt1
和pt2
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
【讨论】:
太棒了,这正是我想要的,一些小改动以适应我的代码,并且运行良好。非常感谢。以上是关于如何将视图边缘弯曲成弧形的主要内容,如果未能解决你的问题,请参考以下文章