我有一个标签和一个图像视图。我的目标是在标签前面有 3 个动画点,并位于标签末尾的底部,如下所示
我只能在 12promax 上实现这种点移动良好的设计,但是我不知道如何使这种设计始终在不同的手机屏幕尺寸上按预期工作。我该如何编码,以便无论屏幕大小或 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
}
“从主菜单检索框”是
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”评论...
要让动画在从后台返回时恢复,您需要添加一个通知观察器。
为“动画点”视图使用自定义
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()
}
}
还有一个示例控制器,展示了如何使用它,包括当应用程序在前台和后台之间移动时启动/停止动画:
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点动画完整代码
进口粉底 导入 UIKit
开放类 FIOProgressView {
var containerView = UIView()
var progressView = UIView()
var dotsContainerView = UIView()
open class var shared: FIOProgressView {
struct Static {
static let instance: FIOProgressView = FIOProgressView()
}
return Static.instance
}
open func showProgressView(_ view: UIView) {
containerView.frame = view.frame
containerView.center = view.center
containerView.backgroundColor = UIColor(hex: 0xffffff, alpha: 0.3)
progressView.frame = CGRect(x: 0, y: 0, width: 120, height: 40)
progressView.center = view.center
progressView.backgroundColor = .clear
progressView.clipsToBounds = true
progressView.layer.cornerRadius = 10
dotsContainerView.frame = CGRect(x: 0, y: 0, width: 120, height: 40)
dotsContainerView.center = CGPoint(x: progressView.bounds.width / 2, y: progressView.bounds.height / 2)
createDots()
progressView.addSubview(dotsContainerView)
containerView.addSubview(progressView)
view.addSubview(containerView)
}
private func createDots() {
let dotSize: CGFloat = 20
let spacing: CGFloat = 20
for i in 0..<3 {
let dot = UIView(frame: CGRect(x: CGFloat(i) * (dotSize + spacing), y: 10, width: dotSize, height: dotSize))
dot.backgroundColor = UIColor(displayP3Red: 92/255, green: 46/255, blue: 126/255, alpha: 1.0)
dot.layer.cornerRadius = dotSize / 2
dotsContainerView.addSubview(dot)
animateDot(dot, delay: TimeInterval(i) * 0.3)
}
}
private func animateDot(_ dot: UIView, delay: TimeInterval) {
UIView.animate(withDuration: 0.5, delay: delay, options: [.autoreverse, .repeat], animations: {
dot.transform = CGAffineTransform(scaleX: 0.2, y: 0.2)
}, completion: nil)
}
open func hideProgressView() {
DispatchQueue.main.async {
self.containerView.removeFromSuperview()
}
}
}