使用 CAGradientLayer 旋转 UIControl 未正确更新 Swift
Rotating UIControl with CAGradientLayer not updating correctly Swift
我没有使用普通按钮,而是将 UIControl
子类化,因为我需要为其添加渐变。我还有一种方法可以添加阴影和 activity 指示器(在下图中不可见)作为有状态按钮,以阻止用户在(例如)进行 API 调用时敲击按钮.
尝试让 UIControl
旋转真的很棘手,为了能够做到这一点,我将阴影作为单独的视图添加到包含 UIControl
的容器视图中,所以可以添加阴影。
现在的问题是控件的行为与旋转视图不太一样 - 让我向您展示上下文的屏幕抓取:
这是中间旋转,但肉眼几乎可以看到 - 图像显示渐变是蓝色 UIView
长度的 75%图片。
https://github.com/stevencurtis/statefulbutton
为了执行此旋转,我删除了 shadowview
,然后将渐变框架的框架更改为其边界,这就是问题所在。
func viewRotated() {
CATransaction.setDisableActions(true)
shadowView!.removeFromSuperview()
shadowView!.frame = self.frame
shadowView!.layer.masksToBounds = false
shadowView!.layer.shadowOffset = CGSize(width: 0, height: 3)
shadowView!.layer.shadowRadius = 3
shadowView!.layer.shadowOpacity = 0.3
shadowView!.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 20, height: 20)).cgPath
shadowView!.layer.shouldRasterize = true
shadowView!.layer.rasterizationScale = UIScreen.main.scale
self.gradientViewLayer.frame = self.bounds
self.selectedViewLayer.frame = self.bounds
CATransaction.commit()
self.insertSubview(shadowView!, at: 0)
}
所以这个旋转方法是通过父视图控制器调用的:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { context in
context.viewController(forKey: UITransitionContextViewControllerKey.from)
//inform the loginButton that it is being rotated
self.loginButton.viewRotated()
}, completion: { context in
// can call here when completed the transition
})
}
我知道这就是问题所在,而且我想现在并没有像 UIView
那样采取同样的行动。现在的问题是我已经尝试了很多方法来让它工作,但我最好的解决方案(上面)并不完全存在。
建议使用 UIButton
来使用渐变图像没有帮助(请不要建议使用渐变图像作为 UIButton 的背景,我试过这个) 或第三方库。这是我的作品,它可以正常工作,但我不能接受,我想让它像通常的视图一样工作(或者至少知道为什么不可以)。我也尝试了上面的其他解决方案,并选择了我自己的 UIControl
。我知道如果有 API 调用,我可以锁定视图,或者使用其他方式阻止用户多次按下按钮。我正在尝试修复我的解决方案,而不是发明使用 CAGradientLayer
.
解决此问题的方法
问题:我需要制作一个 UIControlView
并以 CAGradientLayer
作为背景,以与 UIView
相同的方式旋转,并且不会出现上图中显示的问题。
这是工作代码:
https://gist.github.com/alldne/22d340b36613ae5870b3472fa1c64654
这些是我对您的代码的建议:
1。设置大小和子图层位置的适当位置
视图(即您的按钮)的大小是在布局完成后确定的。你应该做的只是在布局后设置适当的子层大小。所以我建议你在 layoutSubviews
.
中设置渐变子图层的大小和位置
override func layoutSubviews() {
super.layoutSubviews()
let center = CGPoint(x: self.bounds.width / 2, y: self.bounds.height / 2)
selectedViewLayer.bounds = self.bounds
selectedViewLayer.position = center
gradientViewLayer.bounds = self.bounds
gradientViewLayer.position = center
}
2。您不需要使用额外的视图来绘制阴影
删除shadowView
并仅设置图层属性:
layer.shadowOffset = CGSize(width: 0, height: 3)
layer.shadowRadius = 3
layer.shadowOpacity = 0.3
layer.shadowColor = UIColor.black.cgColor
clipsToBounds = false
如果你必须使用额外的视图来绘制阴影,那么你可以在 init()
中添加一次视图并在 layoutSubviews
中设置合适的大小和位置,或者你可以通过编程设置自动父视图的布局约束。
3。动画时长&计时功能
设置合适的尺寸后,渐变图层和容器视图的动画效果不同步。
好像是:
- 在旋转过渡期间,协调器(
UIViewControllerTransitionCoordinator
)有自己的过渡持续时间和缓动函数。
- 并且持续时间和缓动功能自动应用于所有子视图 (
UIView
)。
- 但是,如果没有关联的
UIView
,这些值将不会应用于 CALayer
。因此,它使用 CoreAnimation 的默认计时功能和持续时间。
要同步动画,请明确设置动画持续时间和计时函数,如下所示:
class ViewController: UIViewController {
...
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
CATransaction.setAnimationDuration(coordinator.transitionDuration)
CATransaction.setAnimationTimingFunction(coordinator.completionCurve.timingFunction)
}
...
}
// Swift 4
extension UIView.AnimationCurve {
var timingFunction: CAMediaTimingFunction {
let functionName: CAMediaTimingFunctionName
switch self {
case .easeIn:
functionName = kCAMediaTimingFunctionEaseIn as CAMediaTimingFunctionName
case .easeInOut:
functionName = kCAMediaTimingFunctionEaseInEaseOut as CAMediaTimingFunctionName
case .easeOut:
functionName = kCAMediaTimingFunctionEaseOut as CAMediaTimingFunctionName
case .linear:
functionName = kCAMediaTimingFunctionLinear as CAMediaTimingFunctionName
}
return CAMediaTimingFunction(name: functionName as String)
}
}
我没有使用普通按钮,而是将 UIControl
子类化,因为我需要为其添加渐变。我还有一种方法可以添加阴影和 activity 指示器(在下图中不可见)作为有状态按钮,以阻止用户在(例如)进行 API 调用时敲击按钮.
尝试让 UIControl
旋转真的很棘手,为了能够做到这一点,我将阴影作为单独的视图添加到包含 UIControl
的容器视图中,所以可以添加阴影。
现在的问题是控件的行为与旋转视图不太一样 - 让我向您展示上下文的屏幕抓取:
这是中间旋转,但肉眼几乎可以看到 - 图像显示渐变是蓝色 UIView
长度的 75%图片。
https://github.com/stevencurtis/statefulbutton
为了执行此旋转,我删除了 shadowview
,然后将渐变框架的框架更改为其边界,这就是问题所在。
func viewRotated() {
CATransaction.setDisableActions(true)
shadowView!.removeFromSuperview()
shadowView!.frame = self.frame
shadowView!.layer.masksToBounds = false
shadowView!.layer.shadowOffset = CGSize(width: 0, height: 3)
shadowView!.layer.shadowRadius = 3
shadowView!.layer.shadowOpacity = 0.3
shadowView!.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 20, height: 20)).cgPath
shadowView!.layer.shouldRasterize = true
shadowView!.layer.rasterizationScale = UIScreen.main.scale
self.gradientViewLayer.frame = self.bounds
self.selectedViewLayer.frame = self.bounds
CATransaction.commit()
self.insertSubview(shadowView!, at: 0)
}
所以这个旋转方法是通过父视图控制器调用的:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { context in
context.viewController(forKey: UITransitionContextViewControllerKey.from)
//inform the loginButton that it is being rotated
self.loginButton.viewRotated()
}, completion: { context in
// can call here when completed the transition
})
}
我知道这就是问题所在,而且我想现在并没有像 UIView
那样采取同样的行动。现在的问题是我已经尝试了很多方法来让它工作,但我最好的解决方案(上面)并不完全存在。
建议使用 UIButton
来使用渐变图像没有帮助(请不要建议使用渐变图像作为 UIButton 的背景,我试过这个) 或第三方库。这是我的作品,它可以正常工作,但我不能接受,我想让它像通常的视图一样工作(或者至少知道为什么不可以)。我也尝试了上面的其他解决方案,并选择了我自己的 UIControl
。我知道如果有 API 调用,我可以锁定视图,或者使用其他方式阻止用户多次按下按钮。我正在尝试修复我的解决方案,而不是发明使用 CAGradientLayer
.
问题:我需要制作一个 UIControlView
并以 CAGradientLayer
作为背景,以与 UIView
相同的方式旋转,并且不会出现上图中显示的问题。
这是工作代码:
https://gist.github.com/alldne/22d340b36613ae5870b3472fa1c64654
这些是我对您的代码的建议:
1。设置大小和子图层位置的适当位置
视图(即您的按钮)的大小是在布局完成后确定的。你应该做的只是在布局后设置适当的子层大小。所以我建议你在 layoutSubviews
.
override func layoutSubviews() {
super.layoutSubviews()
let center = CGPoint(x: self.bounds.width / 2, y: self.bounds.height / 2)
selectedViewLayer.bounds = self.bounds
selectedViewLayer.position = center
gradientViewLayer.bounds = self.bounds
gradientViewLayer.position = center
}
2。您不需要使用额外的视图来绘制阴影
删除shadowView
并仅设置图层属性:
layer.shadowOffset = CGSize(width: 0, height: 3)
layer.shadowRadius = 3
layer.shadowOpacity = 0.3
layer.shadowColor = UIColor.black.cgColor
clipsToBounds = false
如果你必须使用额外的视图来绘制阴影,那么你可以在 init()
中添加一次视图并在 layoutSubviews
中设置合适的大小和位置,或者你可以通过编程设置自动父视图的布局约束。
3。动画时长&计时功能
设置合适的尺寸后,渐变图层和容器视图的动画效果不同步。
好像是:
- 在旋转过渡期间,协调器(
UIViewControllerTransitionCoordinator
)有自己的过渡持续时间和缓动函数。 - 并且持续时间和缓动功能自动应用于所有子视图 (
UIView
)。 - 但是,如果没有关联的
UIView
,这些值将不会应用于CALayer
。因此,它使用 CoreAnimation 的默认计时功能和持续时间。
要同步动画,请明确设置动画持续时间和计时函数,如下所示:
class ViewController: UIViewController {
...
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
CATransaction.setAnimationDuration(coordinator.transitionDuration)
CATransaction.setAnimationTimingFunction(coordinator.completionCurve.timingFunction)
}
...
}
// Swift 4
extension UIView.AnimationCurve {
var timingFunction: CAMediaTimingFunction {
let functionName: CAMediaTimingFunctionName
switch self {
case .easeIn:
functionName = kCAMediaTimingFunctionEaseIn as CAMediaTimingFunctionName
case .easeInOut:
functionName = kCAMediaTimingFunctionEaseInEaseOut as CAMediaTimingFunctionName
case .easeOut:
functionName = kCAMediaTimingFunctionEaseOut as CAMediaTimingFunctionName
case .linear:
functionName = kCAMediaTimingFunctionLinear as CAMediaTimingFunctionName
}
return CAMediaTimingFunction(name: functionName as String)
}
}