(Swift 3) - 如何在自定义 UIView 中包含动画
(Swift 3) - How to include animations in custom UIView
我有一个名为 View
的 UIView
subclass,其中我希望 "ripple" 从用户双击的任何地方向外移动(以及预成型其他绘图功能)。这是我的draw
方法的相关部分,目前:
override func draw(_ rect: CGRect) {
for r in ripples {
r.paint()
}
}
这就是 r.paint()
的实现方式,在 class Ripple
:
func paint() {
let c = UIColor(white: CGFloat(1.0 - (Float(iter))/100), alpha: CGFloat(1))
print(iter, " - ",Float(iter)/100)
c.setFill()
view.fillCircle(center: CGPoint(x:x,y:y), radius: CGFloat(iter))
}
iter
应该每 0.1 秒增加一个 Timer
,它在 Ripple
:
的构造函数中启动
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: move)
move
实现如下:
func move(_ timer: Timer) {
while (tier<100) {
iter += 1
DispatchQueue.main.async {
self.view.setNeedsDisplay()
}
}
timer.invalidate()
}
应该发生的是,当计时器触发时每 0.1 秒,它会递增 iter
并告诉 View
重绘,然后它将使用 Ripple.paint()
方法,它使用 iter
来确定圆的颜色和半径。
然而,正如使用 print
语句所揭示的那样,实际发生的是 Timer
在重绘实际发生之前全部触发 100 次。我尝试通过将 DispatchQueue.main.async
替换为 DispatchQueue.main.sync
来解决这个问题,但这只会让应用程序挂起。我究竟做错了什么?如果这不是解决问题的正确方法,那么在应用程序仍然可以执行其他功能(例如产生更多涟漪)的同时,我怎样才能获得平滑的涟漪动画(其中包括一个在改变颜色的同时增长的圆圈)? (这意味着多个涟漪需要能够同时起作用。)
您可以通过 CABasicAnimation
和自定义 CALayer
实现您想要的效果,例如:
class MyLayer : CALayer
{
var iter = 0
override class func needsDisplay(forKey key: String) -> Bool
{
let result = super.needsDisplay(forKey: key)
return result || key == "iter"
}
override func draw(in ctx: CGContext) {
UIGraphicsPushContext(ctx)
UIColor.red.setFill()
ctx.fill(self.bounds)
UIGraphicsPopContext()
NSLog("Drawing, \(iter)")
}
}
class MyView : UIView
{
override class var layerClass: Swift.AnyClass {
get {
return MyLayer.self
}
}
}
class ViewController: UIViewController {
let myview = MyView()
override func viewDidLoad() {
super.viewDidLoad()
myview.frame = self.view.bounds
self.view.addSubview(myview)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let animation = CABasicAnimation(keyPath: "iter")
animation.fromValue = 0
animation.toValue = 100
animation.duration = 10.0
myview.layer.add(animation, forKey: "MyIterAnimation")
(myview.layer as! MyLayer).iter = 100
}
}
自定义 UIView,例如 Swift 3 和 Swift 4
的带有动画的 AlertView
您可以使用扩展程序:
extension UIVIew{
func customAlertView(frame: CGRect, message: String, color: UIColor, startY: CGFloat, endY: CGFloat) -> UIVIew{
//Adding label to view
let label = UILabel()
label.frame = CGRect(x: 0, y: 0, width: frame.width, height:70)
label.textAlignment = .center
label.textColor = .white
label.numberOfLines = 0
label.text = message
self.addSubview(label)
self.backgroundColor = color
//Adding Animation to view
UIView.animate(withDuration:0.5, delay: 0, options:
[.curveEaseOut], animations:{
self.frame = CGRect(x: 0, y: startY, width: frame.width, height: 64)
}) { _ in
UIView.animate(withDuration: 0.5, delay: 4, options: [.curveEaseOut], animations: {
self.frame = CGRect(x: 0, y: endY, width: frame.width, height:64)
}, completion: {_ in
self.removeFromSuperview()
})
}
return self
}
并且您可以在 class 中使用它。这个例子 startY 是当你有一个 navigationController:
class MyClassViewController: UIViewController{
override func viewDidLoad(){
super.viewDidLoad()
self.view.addSubView(UIView().addCustomAlert(frame: self.view.frame, message: "No internet connection", color: .red, startY:64, endY:-64))
}
}
我有一个名为 View
的 UIView
subclass,其中我希望 "ripple" 从用户双击的任何地方向外移动(以及预成型其他绘图功能)。这是我的draw
方法的相关部分,目前:
override func draw(_ rect: CGRect) {
for r in ripples {
r.paint()
}
}
这就是 r.paint()
的实现方式,在 class Ripple
:
func paint() {
let c = UIColor(white: CGFloat(1.0 - (Float(iter))/100), alpha: CGFloat(1))
print(iter, " - ",Float(iter)/100)
c.setFill()
view.fillCircle(center: CGPoint(x:x,y:y), radius: CGFloat(iter))
}
iter
应该每 0.1 秒增加一个 Timer
,它在 Ripple
:
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: move)
move
实现如下:
func move(_ timer: Timer) {
while (tier<100) {
iter += 1
DispatchQueue.main.async {
self.view.setNeedsDisplay()
}
}
timer.invalidate()
}
应该发生的是,当计时器触发时每 0.1 秒,它会递增 iter
并告诉 View
重绘,然后它将使用 Ripple.paint()
方法,它使用 iter
来确定圆的颜色和半径。
然而,正如使用 print
语句所揭示的那样,实际发生的是 Timer
在重绘实际发生之前全部触发 100 次。我尝试通过将 DispatchQueue.main.async
替换为 DispatchQueue.main.sync
来解决这个问题,但这只会让应用程序挂起。我究竟做错了什么?如果这不是解决问题的正确方法,那么在应用程序仍然可以执行其他功能(例如产生更多涟漪)的同时,我怎样才能获得平滑的涟漪动画(其中包括一个在改变颜色的同时增长的圆圈)? (这意味着多个涟漪需要能够同时起作用。)
您可以通过 CABasicAnimation
和自定义 CALayer
实现您想要的效果,例如:
class MyLayer : CALayer
{
var iter = 0
override class func needsDisplay(forKey key: String) -> Bool
{
let result = super.needsDisplay(forKey: key)
return result || key == "iter"
}
override func draw(in ctx: CGContext) {
UIGraphicsPushContext(ctx)
UIColor.red.setFill()
ctx.fill(self.bounds)
UIGraphicsPopContext()
NSLog("Drawing, \(iter)")
}
}
class MyView : UIView
{
override class var layerClass: Swift.AnyClass {
get {
return MyLayer.self
}
}
}
class ViewController: UIViewController {
let myview = MyView()
override func viewDidLoad() {
super.viewDidLoad()
myview.frame = self.view.bounds
self.view.addSubview(myview)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let animation = CABasicAnimation(keyPath: "iter")
animation.fromValue = 0
animation.toValue = 100
animation.duration = 10.0
myview.layer.add(animation, forKey: "MyIterAnimation")
(myview.layer as! MyLayer).iter = 100
}
}
自定义 UIView,例如 Swift 3 和 Swift 4
的带有动画的 AlertView您可以使用扩展程序:
extension UIVIew{
func customAlertView(frame: CGRect, message: String, color: UIColor, startY: CGFloat, endY: CGFloat) -> UIVIew{
//Adding label to view
let label = UILabel()
label.frame = CGRect(x: 0, y: 0, width: frame.width, height:70)
label.textAlignment = .center
label.textColor = .white
label.numberOfLines = 0
label.text = message
self.addSubview(label)
self.backgroundColor = color
//Adding Animation to view
UIView.animate(withDuration:0.5, delay: 0, options:
[.curveEaseOut], animations:{
self.frame = CGRect(x: 0, y: startY, width: frame.width, height: 64)
}) { _ in
UIView.animate(withDuration: 0.5, delay: 4, options: [.curveEaseOut], animations: {
self.frame = CGRect(x: 0, y: endY, width: frame.width, height:64)
}, completion: {_ in
self.removeFromSuperview()
})
}
return self
}
并且您可以在 class 中使用它。这个例子 startY 是当你有一个 navigationController:
class MyClassViewController: UIViewController{
override func viewDidLoad(){
super.viewDidLoad()
self.view.addSubView(UIView().addCustomAlert(frame: self.view.frame, message: "No internet connection", color: .red, startY:64, endY:-64))
}
}