效果图:
动画类 XZAddToCartAnimation 的实现:
import UIKit
import Foundation
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
class XZAddToCartAnimation: NSObject {
private var layer: CALayer?
typealias animationFinisnBlock = (_ finish: Bool) -> Void
private var completion: animationFinisnBlock?
override init() {
super.init()
}
/// 开始动画
func startAnimationWithView(addView: UIView, rect: CGRect, finishPoint: CGPoint, completion: @escaping animationFinisnBlock) {
layer = CALayer()
layer!.contents = addView.layer.contents
layer!.contentsGravity = CALayerContentsGravity.resizeAspectFill
// 重置图层尺寸
var rect = rect
rect.size = CGSize(width: 60, height: 60)
layer!.bounds = rect
layer!.cornerRadius = rect.size.width / 2.0
layer!.masksToBounds = true // 圆角
// 添加到window上
let keyWindow = UIApplication.shared.keyWindow
keyWindow?.layer.addSublayer(layer!)
// 开始点
layer!.position = CGPoint(x: rect.origin.x + addView.frame.size.width / 2.0, y: rect.midY)
// 路径
createAnimationWithRect(rect: rect, finishPoint: finishPoint)
// 回调
self.completion = completion
}
/// 上下 bounces 的动画
func shakeAnimation(shakeView: UIView) {
let shakeAnimation = CABasicAnimation(keyPath: "transform.translation.y")
shakeAnimation.duration = 0.25
shakeAnimation.fromValue = NSNumber(value: -5)
shakeAnimation.toValue = NSNumber(value: 5)
shakeAnimation.autoreverses = true
shakeView.layer.add(shakeAnimation, forKey: nil)
}
/// 创建动画
private func createAnimationWithRect(rect: CGRect, finishPoint: CGPoint) {
// 路径动画
let path = UIBezierPath()
path.move(to: layer!.position)
path.addQuadCurve(to: finishPoint, controlPoint: CGPoint(x: screenWidth / 2.0, y: rect.origin.y - 80))
let pathAnimation = CAKeyframeAnimation(keyPath: "position")
pathAnimation.path = path.cgPath
// 旋转动画
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.isRemovedOnCompletion = true
rotateAnimation.fromValue = NSNumber(value: 0)
rotateAnimation.toValue = NSNumber(value: 12)
rotateAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
// 添加动画组合
let groups = CAAnimationGroup()
groups.animations = [pathAnimation, rotateAnimation]
groups.duration = 1.2
groups.isRemovedOnCompletion = false
groups.fillMode = CAMediaTimingFillMode.forwards
groups.delegate = self
layer!.add(groups, forKey: "group")
}
}
// MARK: - CAAnimationDelegate
extension XZAddToCartAnimation: CAAnimationDelegate {
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
if anim == layer!.animation(forKey: "group") {
layer!.removeFromSuperlayer()
layer = nil
completion?(true)
}
}
}
列表页的代码实现
// 列表页
import UIKit
class XZListViewController: UIViewController {
private var iconNumber:Int = 0
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension XZListViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "XZListViewCell", for: indexPath) as! XZListViewCell
cell.addActionCompleted = { imageView in
var rect = tableView.rectForRow(at: indexPath)
rect.origin.y = rect.origin.y - tableView.contentOffset.y
var imageRect = imageView.frame
imageRect.origin.y = rect.origin.y + imageRect.origin.y
XZAddToCartAnimation().startAnimationWithView(addView: imageView, rect: imageRect, finishPoint: CGPoint(x: screenWidth / 4 * 3, y: screenHeight - 49), completion: { (finish) in
if let tabBarBtn = self.tabBarController?.tabBar.subviews[3]
{
XZAddToCartAnimation().shakeAnimation(shakeView: tabBarBtn)
self.iconNumber += 1
self.tabBarController?.tabBar.xz_showBadges(in: 2, num: self.iconNumber)
// 测试移除功能
if self.iconNumber == 5 {
self.tabBarController?.tabBar.xz_hiddenBadges(in: 2)
}
}
})
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
XZListViewCell的实现:
class XZListViewCell: UITableViewCell {
@IBOutlet weak var xz_imageView: UIImageView!
var addActionCompleted: ((_ imageView: UIImageView)->Void)?
override func awakeFromNib() {
super.awakeFromNib()
}
/// 加入购物车
@IBAction func addToCartAction(_ sender: UIButton) {
addActionCompleted?(xz_imageView)
}
}
tabBar的效果修改:
import UIKit
/// 底部tabBar的总数量
let tabBarCount = 3
/// 直接添加 UILabel
extension UITabBar {
/// 显示 badge
func xz_showBadge(in tabBarIndex: Int, num: Int) {
removeBadge(in: tabBarIndex)
let labelBadge = UILabel()
// 创建
labelBadge.layer.cornerRadius = 10
labelBadge.layer.masksToBounds = true
labelBadge.backgroundColor = .red
labelBadge.text = "\(num)"
labelBadge.font = UIFont.systemFont(ofSize: 10)
labelBadge.tag = 800 + tabBarIndex
labelBadge.textAlignment = .center
let tabFrame = self.frame
// 位置
let percentX = (CGFloat(tabBarIndex) + 0.6) / CGFloat(tabBarCount)
let x = ceilf(Float(percentX * tabFrame.size.width))
let y = ceilf(Float(0.1 * tabFrame.size.height))
labelBadge.frame = CGRect(x: CGFloat(x), y: CGFloat(y), width: 20, height: 20)
// 添加红点
self.addSubview(labelBadge)
}
/// 隐藏 badge
func xz_hiddenBadge(in tabBarIndex: Int) {
removeBadge(in: tabBarIndex)
}
/// 移除 badge
private func removeBadge(in tabBarIndex: Int) {
for sub in subviews {
if sub.tag == 800 + tabBarIndex {
sub.removeFromSuperview()
}
}
}
}
/// 使用关联属性添加 UILabel
extension UITabBar {
struct associatedKey {
static var key = "xz_badge"
}
private var xz_labelBadge: UILabel? {
set {
if let newValue = newValue {
objc_setAssociatedObject(self, &(associatedKey.key), newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
get {
return objc_getAssociatedObject(self, &(associatedKey.key)) as? UILabel
}
}
/// 显示 badge
func xz_showBadges(in tabBarIndex: Int, num: Int) {
/// 防止重复添加
var hasBadge = false
for sub in subviews {
if sub == xz_labelBadge {
hasBadge = true
}
}
if hasBadge == false {
xz_labelBadge = UILabel()
// 创建
xz_labelBadge?.layer.cornerRadius = 10
xz_labelBadge?.layer.masksToBounds = true
xz_labelBadge?.backgroundColor = .red
xz_labelBadge?.font = UIFont.systemFont(ofSize: 10)
xz_labelBadge?.textAlignment = .center
let tabFrame = self.frame
// 位置
let percentX = (CGFloat(tabBarIndex) + 0.6) / CGFloat(tabBarCount)
let x = ceilf(Float(percentX * tabFrame.size.width))
let y = ceilf(Float(0.1 * tabFrame.size.height))
xz_labelBadge?.frame = CGRect(x: CGFloat(x), y: CGFloat(y), width: 20, height: 20)
// 添加红点
self.addSubview(xz_labelBadge!)
}
xz_labelBadge?.text = "\(num)"
}
/// 隐藏 badge
func xz_hiddenBadges(in tabBarIndex: Int) {
removeBadges(in: tabBarIndex)
}
/// 移除 badge
private func removeBadges(in tabBarIndex: Int) {
for sub in subviews {
if sub == xz_labelBadge {
sub.removeFromSuperview()
}
}
}
}