【发布时间】:2021-08-20 03:33:08
【问题描述】:
我想重新创建一个斜角效果圆形UIButton 子类,如下图所示(但使用用户选择的基色)。我对如何设置 CAGradientLayer / 掩蔽 CAShapeLayer 的边界感到困惑。
我想像在 SwiftUI 中那样组合几个 CAShapeLayers。这会根据需要呈现(无渐变)。
但是,当使用先前渲染的 CAShapeLayer 屏蔽 CAGradientLayer 时,渐变不会明显绘制。
我认为我的错误与边界/框架设置有关。当我将子层添加到layoutSubviews() 时,会渲染部分渐变环。此外,用户色环遵循父 VC 设置的约束,但其他层不遵循。
我已经阅读了几篇关于将CAShapeLayer 用作CAGradientLayer 的掩码的帖子(12、3、4),但我不明白如何正确设置框架对于这些多层。
extension BeveledButton {
/// Draws yellow ring in desired location
func makeEmbossmentRingAsShapeLayer() -> CAShapeLayer {
let pathFrame = frame.insetBy(dx: insetBevel, dy: insetBevel)
let path = CGPath.circleStroked(width: bevelRingWidth, inFrame: pathFrame)
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.yellow.cgColor
return shape
}
/// Does not draw gradient (visibly) where yellow ring was
func makeEmbossmentRingAsGradientLayer() -> CAGradientLayer {
let pathFrame = frame.insetBy(dx: insetBevel, dy: insetBevel)
let path = CGPath.circleStroked(width: bevelRingWidth, inFrame: frame)
let mask = CAShapeLayer()
mask.path = path
mask.fillColor = UIColor.black.cgColor
mask.lineWidth = 4
mask.frame = pathFrame
let gradient = CAGradientLayer(colors: [UIColor.orange, UIColor.green], in: pathFrame)
gradient.frame = pathFrame
gradient.mask = mask
return gradient
}
}
extension CGPath {
static func circle(inFrame: CGRect) -> CGPath {
CGPath(ellipseIn: inFrame, transform: nil)
}
static func circleStroked(width: CGFloat, inFrame: CGRect) -> CGPath {
var path = CGPath(ellipseIn: inFrame, transform: nil)
path = path.copy(strokingWithWidth: width, lineCap: .round, lineJoin: .round, miterLimit: 0)
return path
}
}
extension CAGradientLayer {
convenience init(colors: [UIColor], in frame: CGRect) {
self.init()
self.colors = colors.map(\.cgColor)
self.frame = frame
}
}
import UIKit
import Combine
class BeveledButton: UIButton {
weak var userColor: CurrentValueSubject<UIColor,Never>?
private var updates: AnyCancellable? = nil
private lazy var outerRing: CAShapeLayer = makeOuterRing()
private lazy var innerReflection: CAShapeLayer = makeReflectionCircle()
private lazy var userColorCircle: CAShapeLayer = makeUserColorCircle()
private lazy var embossmentRing: CAGradientLayer = makeEmbossmentRingAsGradientLayer()
// private lazy var embossmentRing: CAShapeLayer = makeEmbossmentRingAsShapeLayer()
init(frame: CGRect, userColor: CurrentValueSubject<UIColor,Never>) {
self.userColor = userColor
super.init(frame: frame)
setupRings()
updateUserColor(userColor)
}
required init?(coder: NSCoder) { fatalError("") }
private let outerRingWidth: CGFloat = 2.5
private let inset: CGFloat = 3
private let insetReflection: CGFloat = 5
private lazy var insetBevel: CGFloat = inset + 1
private let bevelRingWidth: CGFloat = 2
}
private extension BeveledButton {
func setupRings() {
layer.addSublayer(outerRing)
layer.addSublayer(userColorCircle)
layer.addSublayer(innerReflection)
layer.addSublayer(embossmentRing)
}
func makeOuterRing() -> CAShapeLayer {
let path = CGPath.circle(inFrame: frame)
let shape = CAShapeLayer()
shape.fillColor = UIColor.clear.cgColor
shape.strokeColor = UIColor.lightGray.cgColor
shape.lineWidth = outerRingWidth
shape.path = path
return shape
}
func makeUserColorCircle() -> CAShapeLayer {
let pathFrame = frame.insetBy(dx: inset, dy: inset)
let path = CGPath.circle(inFrame: pathFrame)
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = userColor?.value.cgColor
return shape
}
func makeReflectionCircle() -> CAShapeLayer {
let pathFrame = frame.insetBy(dx: insetReflection, dy: insetReflection)
let path = CGPath.circle(inFrame: pathFrame)
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.lightGray.cgColor.copy(alpha: 0.4)
return shape
}
func updateUserColor(_ userColor: CurrentValueSubject<UIColor,Never>) {
updates = userColor.sink { [weak self] color in
UIView.animate(withDuration: 0.3) {
self?.userColorCircle.fillColor = color.cgColor
}
}
}
}
【问题讨论】:
标签: ios uikit cagradientlayer