swift iOS 中的 3 个点的动画
Animating 3 dots in swift iOS
我有一个标签和一个图像视图。我的目标是让 3 个点在标签的前面设置动画,并在标签的底部显示如下
我只能在 12promax 上使点移动得很好,但是我无法弄清楚如何使它始终按预期在不同的 phone 屏幕尺寸上工作。我该如何编码,以便无论屏幕大小或 uilabel fontsize 都能达到相同的结果?
动画 ImageView(3 个点)
func showAnimatingDotsInImageView(dots: UIImageView)
{
let newX = view.bounds.width / 896 * 20
let lay = CAReplicatorLayer()
lay.frame = CGRect(x: newX,y: 0,width: dots.bounds.width,height: dots.bounds.height)
let bar = CALayer()
bar.frame = CGRect(x: 0,y: (dots.bounds.height/2) + 8 ,width: 8,height: 8) //make the objs smaller or bigger
bar.cornerRadius = bar.frame.width / 2 //make a circle, if you uncomment this you will get rects
bar.backgroundColor = UIColor.black.cgColor //colour of the objs
lay.addSublayer(bar)
lay.instanceCount = 3 //How many instances / objs do you want to see
lay.instanceTransform = CATransform3DMakeTranslation(15, 0, 0) //1st arg is the spacing between the instances
let anim = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
anim.fromValue = 1.0
anim.toValue = 0.2
anim.duration = 1
anim.repeatCount = .infinity
bar.add(anim, forKey: nil)
lay.instanceDelay = anim.duration / Double(lay.instanceCount)
dots.layer.addSublayer(lay) // add to the view
}
'Retrieving boxes from the main menu' 是 UILabel
并且点在 UIImageView
上动画
你已经很接近了...只要做一些改变就可以让你到达你想要的位置。
如果您将 UILabel
和 UIImageView
(或普通 UIView
)限制在标签的右边缘,您应该能够将 CAReplicatorLayer
无需担心“屏幕尺寸”。
看看我是如何修改你的代码的:
// baseline = to put the bottom of the dots at the baseline of the text in the label
// dotXOffset = gap between end of label and first dot
// dotSize = dot width and height
// dotSpacing = gap between dots
func showAnimatingDotsInImageView(dotsView: UIView, baseline: CGFloat, dotXOffset: CGFloat, dotSize: CGFloat, dotSpacing: CGFloat) {
let lay = CAReplicatorLayer()
let bar = CALayer()
bar.frame = CGRect(x: dotXOffset, y: baseline - dotSize, width: dotSize, height: dotSize)
bar.cornerRadius = bar.frame.width / 2 // we want round dots
bar.backgroundColor = UIColor.black.cgColor
lay.addSublayer(bar)
lay.instanceCount = 3 //How many instances / objs do you want to see
lay.instanceTransform = CATransform3DMakeTranslation(dotSpacing, 0, 0) //1st arg is the spacing between the instances
let anim = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
anim.fromValue = 1.0
anim.toValue = 0.2
anim.duration = 1
anim.repeatCount = .infinity
bar.add(anim, forKey: nil)
lay.instanceDelay = anim.duration / Double(lay.instanceCount)
dotsView.layer.addSublayer(lay) // add to the view
}
这是一个完整的例子:
class SimpleViewController: UIViewController {
let testLabel = UILabel()
let testDotsView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
testLabel.font = .systemFont(ofSize: 24.0)
testLabel.text = "Retrieving boxes"
// so we can see the label frame
testLabel.backgroundColor = .cyan
testLabel.translatesAutoresizingMaskIntoConstraints = false
testDotsView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(testLabel)
view.addSubview(testDotsView)
// always respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// let's constrain the label
// 40-pts from Leading
// 40-pts from Bottom
testLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
testLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
// constrain dots view to
// Top of label
// Trailing of label
testDotsView.topAnchor.constraint(equalTo: testLabel.topAnchor),
testDotsView.leadingAnchor.constraint(equalTo: testLabel.trailingAnchor, constant: 0.0),
// dots image view Width and Height can be 0 (we can draw the layer outside the bounds)
testDotsView.heightAnchor.constraint(equalToConstant: 0.0),
testDotsView.widthAnchor.constraint(equalToConstant: 0.0),
])
// get the label font's baseline y-value
let bl: CGFloat = testLabel.font.ascender
showAnimatingDotsInImageView(dotsView: testDotsView, baseline: bl, dotXOffset: 4.0, dotSize: 4.0, dotSpacing: 8.0)
}
// baseline = to put the bottom of the dots at the baseline of the text in the label
// dotXOffset = gap between end of label and first dot
// dotSize = dot width and height
// dotSpacing = gap between dots
func showAnimatingDotsInImageView(dotsView: UIView, baseline: CGFloat, dotXOffset: CGFloat, dotSize: CGFloat, dotSpacing: CGFloat) {
let lay = CAReplicatorLayer()
let bar = CALayer()
bar.frame = CGRect(x: dotXOffset, y: baseline - dotSize, width: dotSize, height: dotSize)
bar.cornerRadius = bar.frame.width / 2 // we want round dots
bar.backgroundColor = UIColor.black.cgColor
lay.addSublayer(bar)
lay.instanceCount = 3 //How many instances / objs do you want to see
lay.instanceTransform = CATransform3DMakeTranslation(dotSpacing, 0, 0) //1st arg is the spacing between the instances
let anim = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
anim.fromValue = 1.0
anim.toValue = 0.2
anim.duration = 1
anim.repeatCount = .infinity
bar.add(anim, forKey: nil)
lay.instanceDelay = anim.duration / Double(lay.instanceCount)
dotsView.layer.addSublayer(lay) // add to the view
}
}
编辑 - 回应“krishan kumar”评论...
要让动画在从后台返回时恢复,您需要添加一个 Notification Observer。
为“动画点”视图使用自定义 UIView
子类会容易得多,所以这里有一个简单的例子:
class DotsView: UIView {
// baseline = to put the bottom of the dots at the baseline of the text in the label
// dotXOffset = gap between end of label and first dot
// dotSize = dot width and height
// dotSpacing = gap between dots
public var baseline: CGFloat = 0
public var dotXOffset: CGFloat = 4.0
public var dotSize: CGFloat = 4.0
public var dotSpacing: CGFloat = 8.0
private let lay = CAReplicatorLayer()
private let bar = CALayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
lay.addSublayer(bar)
layer.addSublayer(lay)
}
public func beginAnimating() {
bar.frame = CGRect(x: dotXOffset, y: baseline - dotSize, width: dotSize, height: dotSize)
// we want round dots
bar.cornerRadius = bar.frame.width / 2.0
bar.backgroundColor = UIColor.black.cgColor
//How many instances / objs we want to see
lay.instanceCount = 3
//1st arg is the spacing between the instances
lay.instanceTransform = CATransform3DMakeTranslation(dotSpacing, 0, 0)
let anim = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
anim.fromValue = 1.0
anim.toValue = 0.2
anim.duration = 1
anim.repeatCount = .infinity
bar.add(anim, forKey: nil)
// so the dots animate in sequence
lay.instanceDelay = anim.duration / Double(lay.instanceCount)
}
public func stopAnimating() {
layer.removeAllAnimations()
}
}
以及展示如何使用它的示例控制器,包括 starting/stopping 应用在前景和背景之间移动时的动画:
class SimpleViewController: UIViewController {
let testLabel = UILabel()
// custom DotsView
let testDotsView = DotsView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
testLabel.font = .systemFont(ofSize: 24.0)
testLabel.text = "Retrieving boxes"
// so we can see the label frame
testLabel.backgroundColor = .cyan
testLabel.translatesAutoresizingMaskIntoConstraints = false
testDotsView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(testLabel)
view.addSubview(testDotsView)
// always respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// let's constrain the label
// 40-pts from Leading
// 40-pts from Bottom
testLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
testLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
// constrain dots view to
// Top of label
// Trailing of label
testDotsView.topAnchor.constraint(equalTo: testLabel.topAnchor),
testDotsView.leadingAnchor.constraint(equalTo: testLabel.trailingAnchor, constant: 0.0),
// dots image view Width and Height can be 0 (we can draw the layer outside the bounds)
testDotsView.heightAnchor.constraint(equalToConstant: 0.0),
testDotsView.widthAnchor.constraint(equalToConstant: 0.0),
])
// get the label font's baseline y-value
testDotsView.baseline = testLabel.font.ascender
// use defaults or set values here
//testDotsView.dotXOffset = 4.0
//testDotsView.dotSize = 4.0
//testDotsView.dotSpacing = 8.0
testDotsView.beginAnimating()
// we want to
// Stop the Dots animation when the app goes into the Background, and
// Start the Dots animation when the app Enters the Foreground
NotificationCenter.default.addObserver(self, selector: #selector(myEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(myEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
@objc func myEnterBackground() {
testDotsView.stopAnimating()
}
@objc func myEnterForeground() {
testDotsView.beginAnimating()
}
}
我有一个标签和一个图像视图。我的目标是让 3 个点在标签的前面设置动画,并在标签的底部显示如下
我只能在 12promax 上使点移动得很好,但是我无法弄清楚如何使它始终按预期在不同的 phone 屏幕尺寸上工作。我该如何编码,以便无论屏幕大小或 uilabel fontsize 都能达到相同的结果?
动画 ImageView(3 个点)
func showAnimatingDotsInImageView(dots: UIImageView)
{
let newX = view.bounds.width / 896 * 20
let lay = CAReplicatorLayer()
lay.frame = CGRect(x: newX,y: 0,width: dots.bounds.width,height: dots.bounds.height)
let bar = CALayer()
bar.frame = CGRect(x: 0,y: (dots.bounds.height/2) + 8 ,width: 8,height: 8) //make the objs smaller or bigger
bar.cornerRadius = bar.frame.width / 2 //make a circle, if you uncomment this you will get rects
bar.backgroundColor = UIColor.black.cgColor //colour of the objs
lay.addSublayer(bar)
lay.instanceCount = 3 //How many instances / objs do you want to see
lay.instanceTransform = CATransform3DMakeTranslation(15, 0, 0) //1st arg is the spacing between the instances
let anim = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
anim.fromValue = 1.0
anim.toValue = 0.2
anim.duration = 1
anim.repeatCount = .infinity
bar.add(anim, forKey: nil)
lay.instanceDelay = anim.duration / Double(lay.instanceCount)
dots.layer.addSublayer(lay) // add to the view
}
'Retrieving boxes from the main menu' 是 UILabel
并且点在 UIImageView
你已经很接近了...只要做一些改变就可以让你到达你想要的位置。
如果您将 UILabel
和 UIImageView
(或普通 UIView
)限制在标签的右边缘,您应该能够将 CAReplicatorLayer
无需担心“屏幕尺寸”。
看看我是如何修改你的代码的:
// baseline = to put the bottom of the dots at the baseline of the text in the label
// dotXOffset = gap between end of label and first dot
// dotSize = dot width and height
// dotSpacing = gap between dots
func showAnimatingDotsInImageView(dotsView: UIView, baseline: CGFloat, dotXOffset: CGFloat, dotSize: CGFloat, dotSpacing: CGFloat) {
let lay = CAReplicatorLayer()
let bar = CALayer()
bar.frame = CGRect(x: dotXOffset, y: baseline - dotSize, width: dotSize, height: dotSize)
bar.cornerRadius = bar.frame.width / 2 // we want round dots
bar.backgroundColor = UIColor.black.cgColor
lay.addSublayer(bar)
lay.instanceCount = 3 //How many instances / objs do you want to see
lay.instanceTransform = CATransform3DMakeTranslation(dotSpacing, 0, 0) //1st arg is the spacing between the instances
let anim = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
anim.fromValue = 1.0
anim.toValue = 0.2
anim.duration = 1
anim.repeatCount = .infinity
bar.add(anim, forKey: nil)
lay.instanceDelay = anim.duration / Double(lay.instanceCount)
dotsView.layer.addSublayer(lay) // add to the view
}
这是一个完整的例子:
class SimpleViewController: UIViewController {
let testLabel = UILabel()
let testDotsView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
testLabel.font = .systemFont(ofSize: 24.0)
testLabel.text = "Retrieving boxes"
// so we can see the label frame
testLabel.backgroundColor = .cyan
testLabel.translatesAutoresizingMaskIntoConstraints = false
testDotsView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(testLabel)
view.addSubview(testDotsView)
// always respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// let's constrain the label
// 40-pts from Leading
// 40-pts from Bottom
testLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
testLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
// constrain dots view to
// Top of label
// Trailing of label
testDotsView.topAnchor.constraint(equalTo: testLabel.topAnchor),
testDotsView.leadingAnchor.constraint(equalTo: testLabel.trailingAnchor, constant: 0.0),
// dots image view Width and Height can be 0 (we can draw the layer outside the bounds)
testDotsView.heightAnchor.constraint(equalToConstant: 0.0),
testDotsView.widthAnchor.constraint(equalToConstant: 0.0),
])
// get the label font's baseline y-value
let bl: CGFloat = testLabel.font.ascender
showAnimatingDotsInImageView(dotsView: testDotsView, baseline: bl, dotXOffset: 4.0, dotSize: 4.0, dotSpacing: 8.0)
}
// baseline = to put the bottom of the dots at the baseline of the text in the label
// dotXOffset = gap between end of label and first dot
// dotSize = dot width and height
// dotSpacing = gap between dots
func showAnimatingDotsInImageView(dotsView: UIView, baseline: CGFloat, dotXOffset: CGFloat, dotSize: CGFloat, dotSpacing: CGFloat) {
let lay = CAReplicatorLayer()
let bar = CALayer()
bar.frame = CGRect(x: dotXOffset, y: baseline - dotSize, width: dotSize, height: dotSize)
bar.cornerRadius = bar.frame.width / 2 // we want round dots
bar.backgroundColor = UIColor.black.cgColor
lay.addSublayer(bar)
lay.instanceCount = 3 //How many instances / objs do you want to see
lay.instanceTransform = CATransform3DMakeTranslation(dotSpacing, 0, 0) //1st arg is the spacing between the instances
let anim = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
anim.fromValue = 1.0
anim.toValue = 0.2
anim.duration = 1
anim.repeatCount = .infinity
bar.add(anim, forKey: nil)
lay.instanceDelay = anim.duration / Double(lay.instanceCount)
dotsView.layer.addSublayer(lay) // add to the view
}
}
编辑 - 回应“krishan kumar”评论...
要让动画在从后台返回时恢复,您需要添加一个 Notification Observer。
为“动画点”视图使用自定义 UIView
子类会容易得多,所以这里有一个简单的例子:
class DotsView: UIView {
// baseline = to put the bottom of the dots at the baseline of the text in the label
// dotXOffset = gap between end of label and first dot
// dotSize = dot width and height
// dotSpacing = gap between dots
public var baseline: CGFloat = 0
public var dotXOffset: CGFloat = 4.0
public var dotSize: CGFloat = 4.0
public var dotSpacing: CGFloat = 8.0
private let lay = CAReplicatorLayer()
private let bar = CALayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
lay.addSublayer(bar)
layer.addSublayer(lay)
}
public func beginAnimating() {
bar.frame = CGRect(x: dotXOffset, y: baseline - dotSize, width: dotSize, height: dotSize)
// we want round dots
bar.cornerRadius = bar.frame.width / 2.0
bar.backgroundColor = UIColor.black.cgColor
//How many instances / objs we want to see
lay.instanceCount = 3
//1st arg is the spacing between the instances
lay.instanceTransform = CATransform3DMakeTranslation(dotSpacing, 0, 0)
let anim = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
anim.fromValue = 1.0
anim.toValue = 0.2
anim.duration = 1
anim.repeatCount = .infinity
bar.add(anim, forKey: nil)
// so the dots animate in sequence
lay.instanceDelay = anim.duration / Double(lay.instanceCount)
}
public func stopAnimating() {
layer.removeAllAnimations()
}
}
以及展示如何使用它的示例控制器,包括 starting/stopping 应用在前景和背景之间移动时的动画:
class SimpleViewController: UIViewController {
let testLabel = UILabel()
// custom DotsView
let testDotsView = DotsView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
testLabel.font = .systemFont(ofSize: 24.0)
testLabel.text = "Retrieving boxes"
// so we can see the label frame
testLabel.backgroundColor = .cyan
testLabel.translatesAutoresizingMaskIntoConstraints = false
testDotsView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(testLabel)
view.addSubview(testDotsView)
// always respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// let's constrain the label
// 40-pts from Leading
// 40-pts from Bottom
testLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
testLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
// constrain dots view to
// Top of label
// Trailing of label
testDotsView.topAnchor.constraint(equalTo: testLabel.topAnchor),
testDotsView.leadingAnchor.constraint(equalTo: testLabel.trailingAnchor, constant: 0.0),
// dots image view Width and Height can be 0 (we can draw the layer outside the bounds)
testDotsView.heightAnchor.constraint(equalToConstant: 0.0),
testDotsView.widthAnchor.constraint(equalToConstant: 0.0),
])
// get the label font's baseline y-value
testDotsView.baseline = testLabel.font.ascender
// use defaults or set values here
//testDotsView.dotXOffset = 4.0
//testDotsView.dotSize = 4.0
//testDotsView.dotSpacing = 8.0
testDotsView.beginAnimating()
// we want to
// Stop the Dots animation when the app goes into the Background, and
// Start the Dots animation when the app Enters the Foreground
NotificationCenter.default.addObserver(self, selector: #selector(myEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(myEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
@objc func myEnterBackground() {
testDotsView.stopAnimating()
}
@objc func myEnterForeground() {
testDotsView.beginAnimating()
}
}