Swift: UIView.animate 工作意外
Swift: UIView.animate works unexpectedly
我正在尝试为我的应用制作一个 "record button",它是一个带有手势识别器的 UIView。现在我正在实现这样一个功能,即当我单击视图时,我想缩小内圈(红色圆圈),但它最终会发生意想不到的转变。我使用 UIView.animate 函数来执行此操作,下面是我的相关代码:
import UIKit
@IBDesignable
class RecordView: UIView {
@IBInspectable
var borderColor: UIColor = UIColor.clear {
didSet {
self.layer.borderColor = borderColor.cgColor
}
}
@IBInspectable
var borderWidth: CGFloat = 20 {
didSet {
layer.borderWidth = borderWidth
}
}
@IBInspectable
var cornerRadius: CGFloat = 100 {
didSet {
layer.cornerRadius = cornerRadius
}
}
private var fillView = UIView()
private func setupFillView() {
let radius = (self.cornerRadius - self.borderWidth) * 0.95
fillView.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: radius * 2, height: radius * 2))
fillView.center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
fillView.layer.cornerRadius = radius
fillView.backgroundColor = UIColor.red
self.addSubview(fillView)
}
override func layoutSubviews() {
super.layoutSubviews()
setupFillView()
}
func didClick() {
UIView.animate(withDuration: 1.0, animations: {
self.fillView.transform = CGAffineTransform(scaleX: 0.6, y: 0.6)
}) { (true) in
print()
}
}
}
在我的 ViewController 中,我有:
@IBAction func recoreViewTapped(_ sender: UITapGestureRecognizer) {
recordView.didClick()
}
然而它最终产生了这样的效果:
https://media.giphy.com/media/3ohjUQrWt7vfIxzBrG/giphy.gif。我是初学者,真的不知道我的代码有什么问题?
问题是您正在应用转换,这会再次触发 layoutSubviews
,因此 fillView
的 frame
的重置将应用于转换后的视图.
至少有两个选项:
您可以变换整个 RecordButton
视图:
@IBDesignable
class RecordView: UIView {
@IBInspectable
var borderColor: UIColor = .clear { didSet { layer.borderColor = borderColor.cgColor } }
@IBInspectable
var borderWidth: CGFloat = 20 { didSet { layer.borderWidth = borderWidth; setNeedsLayout() } }
private var fillView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
fillView.backgroundColor = .red
self.addSubview(fillView)
}
override func layoutSubviews() {
super.layoutSubviews()
let radius = min(bounds.width, bounds.height) / 2 - borderWidth
fillView.frame = CGRect(origin: CGPoint(x: bounds.midX - radius, y: bounds.midY - radius),
size: CGSize(width: radius * 2, height: radius * 2))
fillView.layer.cornerRadius = radius
}
func didClick() {
UIView.animate(withDuration: 1.0, animations: {
self.transform = CGAffineTransform(scaleX: 0.6, y: 0.6)
}, completion: { _ in
print("completion", #function)
})
}
}
或者您可以将 fillView
放入容器中,然后 fillView
的变换不会触发 [=20] 的 layoutSubviews
=]:
@IBDesignable
class RecordView: UIView {
@IBInspectable
var borderColor: UIColor = .clear { didSet { layer.borderColor = borderColor.cgColor } }
@IBInspectable
var borderWidth: CGFloat = 20 { didSet { layer.borderWidth = borderWidth; setNeedsLayout() } }
private var fillView = UIView()
private var containerView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
fillView.backgroundColor = .red
self.addSubview(containerView)
containerView.addSubview(fillView)
}
override func layoutSubviews() {
super.layoutSubviews()
containerView.frame = bounds
let radius = min(bounds.width, bounds.height) / 2 - borderWidth
fillView.frame = CGRect(origin: CGPoint(x: bounds.midX - radius, y: bounds.midY - radius),
size: CGSize(width: radius * 2, height: radius * 2))
fillView.layer.cornerRadius = radius
}
func didClick() {
UIView.animate(withDuration: 1.0, animations: {
self.fillView.transform = CGAffineTransform(scaleX: 0.6, y: 0.6)
}, completion: { _ in
print("completion", #function)
})
}
}
顺便说一下其他一些小事:
正如你我在别处讨论的那样,初始配置(例如添加子视图)应该从 init
调用。但是任何 frame
/bounds
偶然的东西都应该从 layoutSubviews
.
调用
完全不相关,但是传给UIView.animate(withDuration:animations:completion:)
的completion
的参数是一个布尔值,表示动画是否finished
。如果您不打算使用该布尔值,则应使用 _
,而不是 (true)
。
从长远来看,我建议将 "touches"/手势相关的内容移动到 RecordButton 中。我可以很容易地想象你变得更加狂热:例如一个动画 "touch down"(缩小按钮以呈现 "depress" 氛围)和另一个动画 "lift up"(例如,取消缩小按钮并将圆形 "record" 按钮转换为方形 "stop" 按钮)。然后,您可以让按钮在发生重大事件时通知视图控制器(例如记录或停止识别),但会降低视图控制器本身的复杂性。
protocol RecordViewDelegate: class {
func didTapRecord()
func didTapStop()
}
@IBDesignable
class RecordView: UIView {
@IBInspectable
var borderColor: UIColor = .clear { didSet { layer.borderColor = borderColor.cgColor } }
@IBInspectable
var borderWidth: CGFloat = 20 { didSet { layer.borderWidth = borderWidth; setNeedsLayout() } }
weak var delegate: RecordViewDelegate?
var isRecordButton = true
private var fillView = UIView()
private var containerView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
fillView.backgroundColor = .red
self.addSubview(containerView)
containerView.addSubview(fillView)
}
private var radius: CGFloat { return min(bounds.width, bounds.height) / 2 - borderWidth }
override func layoutSubviews() {
super.layoutSubviews()
containerView.frame = bounds
fillView.frame = CGRect(origin: CGPoint(x: bounds.midX - radius, y: bounds.midY - radius),
size: CGSize(width: radius * 2, height: radius * 2))
fillView.layer.cornerRadius = isRecordButton ? radius : 0
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
UIView.animate(withDuration: 0.25) {
self.fillView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
self.fillView.backgroundColor = UIColor(red: 0.75, green: 0, blue: 0, alpha: 1)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first, containerView.bounds.contains(touch.location(in: containerView)) {
if isRecordButton {
delegate?.didTapRecord()
} else {
delegate?.didTapStop()
}
isRecordButton = !isRecordButton
}
UIView.animate(withDuration: 0.25) {
self.fillView.transform = .identity
self.fillView.backgroundColor = UIColor.red
self.fillView.layer.cornerRadius = self.isRecordButton ? self.radius : 0
}
}
}
产生:
我正在尝试为我的应用制作一个 "record button",它是一个带有手势识别器的 UIView。现在我正在实现这样一个功能,即当我单击视图时,我想缩小内圈(红色圆圈),但它最终会发生意想不到的转变。我使用 UIView.animate 函数来执行此操作,下面是我的相关代码:
import UIKit
@IBDesignable
class RecordView: UIView {
@IBInspectable
var borderColor: UIColor = UIColor.clear {
didSet {
self.layer.borderColor = borderColor.cgColor
}
}
@IBInspectable
var borderWidth: CGFloat = 20 {
didSet {
layer.borderWidth = borderWidth
}
}
@IBInspectable
var cornerRadius: CGFloat = 100 {
didSet {
layer.cornerRadius = cornerRadius
}
}
private var fillView = UIView()
private func setupFillView() {
let radius = (self.cornerRadius - self.borderWidth) * 0.95
fillView.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: radius * 2, height: radius * 2))
fillView.center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
fillView.layer.cornerRadius = radius
fillView.backgroundColor = UIColor.red
self.addSubview(fillView)
}
override func layoutSubviews() {
super.layoutSubviews()
setupFillView()
}
func didClick() {
UIView.animate(withDuration: 1.0, animations: {
self.fillView.transform = CGAffineTransform(scaleX: 0.6, y: 0.6)
}) { (true) in
print()
}
}
}
在我的 ViewController 中,我有:
@IBAction func recoreViewTapped(_ sender: UITapGestureRecognizer) {
recordView.didClick()
}
然而它最终产生了这样的效果: https://media.giphy.com/media/3ohjUQrWt7vfIxzBrG/giphy.gif。我是初学者,真的不知道我的代码有什么问题?
问题是您正在应用转换,这会再次触发 layoutSubviews
,因此 fillView
的 frame
的重置将应用于转换后的视图.
至少有两个选项:
您可以变换整个
RecordButton
视图:@IBDesignable class RecordView: UIView { @IBInspectable var borderColor: UIColor = .clear { didSet { layer.borderColor = borderColor.cgColor } } @IBInspectable var borderWidth: CGFloat = 20 { didSet { layer.borderWidth = borderWidth; setNeedsLayout() } } private var fillView = UIView() override init(frame: CGRect) { super.init(frame: frame) configure() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) configure() } private func configure() { fillView.backgroundColor = .red self.addSubview(fillView) } override func layoutSubviews() { super.layoutSubviews() let radius = min(bounds.width, bounds.height) / 2 - borderWidth fillView.frame = CGRect(origin: CGPoint(x: bounds.midX - radius, y: bounds.midY - radius), size: CGSize(width: radius * 2, height: radius * 2)) fillView.layer.cornerRadius = radius } func didClick() { UIView.animate(withDuration: 1.0, animations: { self.transform = CGAffineTransform(scaleX: 0.6, y: 0.6) }, completion: { _ in print("completion", #function) }) } }
或者您可以将
fillView
放入容器中,然后fillView
的变换不会触发 [=20] 的layoutSubviews
=]:@IBDesignable class RecordView: UIView { @IBInspectable var borderColor: UIColor = .clear { didSet { layer.borderColor = borderColor.cgColor } } @IBInspectable var borderWidth: CGFloat = 20 { didSet { layer.borderWidth = borderWidth; setNeedsLayout() } } private var fillView = UIView() private var containerView = UIView() override init(frame: CGRect) { super.init(frame: frame) configure() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) configure() } private func configure() { fillView.backgroundColor = .red self.addSubview(containerView) containerView.addSubview(fillView) } override func layoutSubviews() { super.layoutSubviews() containerView.frame = bounds let radius = min(bounds.width, bounds.height) / 2 - borderWidth fillView.frame = CGRect(origin: CGPoint(x: bounds.midX - radius, y: bounds.midY - radius), size: CGSize(width: radius * 2, height: radius * 2)) fillView.layer.cornerRadius = radius } func didClick() { UIView.animate(withDuration: 1.0, animations: { self.fillView.transform = CGAffineTransform(scaleX: 0.6, y: 0.6) }, completion: { _ in print("completion", #function) }) } }
顺便说一下其他一些小事:
正如你我在别处讨论的那样,初始配置(例如添加子视图)应该从
init
调用。但是任何frame
/bounds
偶然的东西都应该从layoutSubviews
. 调用
完全不相关,但是传给
UIView.animate(withDuration:animations:completion:)
的completion
的参数是一个布尔值,表示动画是否finished
。如果您不打算使用该布尔值,则应使用_
,而不是(true)
。从长远来看,我建议将 "touches"/手势相关的内容移动到 RecordButton 中。我可以很容易地想象你变得更加狂热:例如一个动画 "touch down"(缩小按钮以呈现 "depress" 氛围)和另一个动画 "lift up"(例如,取消缩小按钮并将圆形 "record" 按钮转换为方形 "stop" 按钮)。然后,您可以让按钮在发生重大事件时通知视图控制器(例如记录或停止识别),但会降低视图控制器本身的复杂性。
protocol RecordViewDelegate: class { func didTapRecord() func didTapStop() } @IBDesignable class RecordView: UIView { @IBInspectable var borderColor: UIColor = .clear { didSet { layer.borderColor = borderColor.cgColor } } @IBInspectable var borderWidth: CGFloat = 20 { didSet { layer.borderWidth = borderWidth; setNeedsLayout() } } weak var delegate: RecordViewDelegate? var isRecordButton = true private var fillView = UIView() private var containerView = UIView() override init(frame: CGRect) { super.init(frame: frame) configure() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) configure() } private func configure() { fillView.backgroundColor = .red self.addSubview(containerView) containerView.addSubview(fillView) } private var radius: CGFloat { return min(bounds.width, bounds.height) / 2 - borderWidth } override func layoutSubviews() { super.layoutSubviews() containerView.frame = bounds fillView.frame = CGRect(origin: CGPoint(x: bounds.midX - radius, y: bounds.midY - radius), size: CGSize(width: radius * 2, height: radius * 2)) fillView.layer.cornerRadius = isRecordButton ? radius : 0 } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { UIView.animate(withDuration: 0.25) { self.fillView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8) self.fillView.backgroundColor = UIColor(red: 0.75, green: 0, blue: 0, alpha: 1) } } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { if let touch = touches.first, containerView.bounds.contains(touch.location(in: containerView)) { if isRecordButton { delegate?.didTapRecord() } else { delegate?.didTapStop() } isRecordButton = !isRecordButton } UIView.animate(withDuration: 0.25) { self.fillView.transform = .identity self.fillView.backgroundColor = UIColor.red self.fillView.layer.cornerRadius = self.isRecordButton ? self.radius : 0 } } }
产生: