前段时间学习了一下swift的基础动画,于是找了个TableViewcell的动画项目来学习学习Folding cell
学习的过程中遇到了一些坑,在这里分享一下,给大家借鉴借鉴
UITableViewController
首先我刚看到这个动画的时候,我先给这个动画进行了分解,在这个动画开始前,应该是先修改了cell 的高度,于是我先着手修改cell的高度
//定义了cell打开时和关闭时的高度
let openCellHeight: CGFloat = 505
let closeCellHeight: CGFloat = 180
//设置所有cell 的初始高度
var cellHeights: [CGFloat] = []
override func viewDidLoad() {
super.viewDidLoad()
cellHeights = Array(repeating: closeCellHeight, count: rowCount)
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath.row]
}
//并在创建的 UITableViewController内重写didSelectorRowAt 方法
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! TestTableViewCell
let cellIsCollapsed = cellHeights[indexPath.row] == closeCellHeight
if cellIsCollapsed {
cellHeights[indexPath.row] = openCellHeight
} else {
cellHeights[indexPath.row] = closeCellHeight
}
tableView.reloadData()
}
这时点击cell会有高度的变化,但是你会发现cell的高度变化是一瞬间的,并不是慢慢变大,于是我查找了一下资料,发现使用tabelView.beginUpdates()和tabelView.endUpdates()来代替tableView.reloadData()实现cell高度渐变的效果,并且使用UIView.animate(withDuration: TimeInterval, animations: () -> Void)来设置了tabelViewcell高度变化的时间差
UIView.animate(withDuration: druation, delay: 0, options: .curveEaseIn, animations: { () -> Void in
tableView.beginUpdates()
tableView.endUpdates()
}, completion: nil)
这时我们就能通过设置druation来控制cell高度变化的时间,但是,这时你会发现点击cell 后整个cell会变成灰色,经过查找后发现这是由cell的selectionStyle引起的,我们需要把cell.selectionStyle设置为UITableViewCellSelectionStyle.none或者在storybord的界面选择cell后修改Selection为None。
UITableTableCell
第一步,我们要实现点击前和点击后cell的view显现两个不同的View,我们需要在cell 内的ContentView中创建两个高度不同的View(这里我们把forgroundView设为cell关闭时的View,constrainView为cell 打开后的View)当cell是对应的高度时就设置该view的alphe为1,另一个着为0。在我们设置动画之前,我们需要设置的forgroundView和constrainView的位置,我的做法是在storyboard为两个view 都设置了上左右和Height的约束,并把forgroundView和constrainView的Top约束连接到UITableTableCell里并设置它们的constant大小相同
@IBOutlet weak var foreguoundTop: NSLayoutConstraint!
@IBOutlet weak var constrainTop: NSLayoutConstraint!
constrainTop.constant = foreguoundTop.constant
第二步,我们先实现一个view能折叠翻转,这时我就想起之前学习的CABasicAnimation的transform.rotation,于是我先试着在forgroundView延底部翻转
animation1 = CABasicAnimation(keyPath: "transform.rotation.x")
animation1.fromValue = 0
animation1.toValue = -CGFloat.pi / 2.0
animation1.duration = 0.13
forgroundView.layer.add(animation1, forKey: "forgroundView")
这时你能发现forgroundView是延中线翻转的我们需要延底部的,这时我们需要设置forgroudView的layer.anchorPoint,默认的View的layer.anchorPoint都为CGPoint(x: 0.5, y: 0.5),这个是View的中心点,要让forgroundView延底部转,我们需要设置layer.anchorPoint为CGPoint(x: 0.5, y: 1),这时你发现forgroundView的确能延底部翻转,但是你会发现修改layer.anchorPoint后forgroundView的位置发生了偏移,往上偏移了半个forgroundView的高度,查阅资料后发现两条公式:
frame.origin.x = position.x - anchorPoint.x * bounds.size.width
frame.origin.y = position.y - anchorPoint.y * bounds.size.height
且position不受anchorPoint影响,所以当我们修改View的anchorPoint时变化的就是frame.origin,如果我们要不影响forgroundView的位置
我们需要先记录forgroundView的frame,修改完anchorPoint后再赋值forgroundView的frame
var imageFrame = forgroundView.frame
forgroundView.layer.anchorPoint = CGPoint(x: 0.5, y: 1)
forgroundView.frame = imageFrame
在这里我要分享一个我遇到的坑,当初我设置animation和修改anchorPoint都是在cell的awakeFormNib()方法里,这里无论我怎么修改forgroundView的frame,forgroun都不会有任何变化,后来查阅资料才发现,原来awakeFormNib的调用是在ViewController创建之前就调用了,这时获取的View的frame都是不对的,所以无法重新赋值,所以我建议修改frame放在viewDidLayoutSubviews或之后的方法里
第三步
我们现在已经可以在不移动forgroundView情况下翻转forgroundView了,但是我们发现中间翻转的时候是有白色的View的,这是需要创建的,这里我就把显现的ImageViews和需要翻转的animationViews分别创建,这时我们就需要获取forgroundView和constainsView的视图赋值给显现的View
public extension UIView {
func setSampleView(frame: CGRect) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(frame.size, false, 0)
guard let context = UIGraphicsGetCurrentContext() else {
print("fail")
return nil
}
context.translateBy(x: frame.origin.x * -1 , y: frame.origin.y * -1 )
layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
这里我扩展了UIView来获取forgroundView和constainsView的视图
let firstImage = constrainView.setSampleView(frame: CGRect(x: constrainView.bounds.minX, y: constrainView.bounds.minY, width: forgroundView.frame.width, height: forgroundView.frame.height))
firstViewBg = UIImageView(image: firstImage)
firstViewBg.frame = CGRect(x: constrainView.bounds.minX, y: constrainView.bounds.minY, width: forgroundView.frame.width, height: forgroundView.frame.height)
这样我们就可以获取以forgroundView和constrainView为图片的ImageViews和animationViews
第四步
这时我们需要把动画连接起来,我看了FoldingCell里它是通过设置动画的beginTime使动画连续起来,而我选择使用animationDidStop()来使动画连接起来,这个方法的调用需要cell继承CAAnimationDelegate
并且需要设置animatiom需要设置delegate和设置isRemoveOnCompletion为false
animation1.isRemovedOnCompletion = false
animation1.delegate = self
//上一个animation结束时就执行下一个动画
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
if anim == firstImageView.layer.animation(forKey: "firstImageView") {
firstImageView.alpha = 0
secondImageView.alpha = 1
secondImagebgView.alpha = 1
firstViewBg.layer.masksToBounds = false
secondImageView.layer.add(animation2, forKey: "secondImageView")
}
}
当我们我们都设置好之后,运行起来之后我们会发现,我们的翻转并没有那种3D的立体感,这个我们需要设置layer.transform
var animationTransform = CATransform3DIdentity
animationTransform.m34 = -2.5 / 2000
firstImageView.layer.transform = animationTransform
CALayer中有一个transform属性便是专门用来控制3D形变的,transform属性默认值为CATransform3DIdentity, 在CATransform3DIdentity结构体中有一个m34允许我们将正交投影修改为有近大远小立体效果的透视投影,其中m34 = -1.0/z,这个z为观察者与控件之间的距离, m34必须在赋值transform之前设置才会生效.
到此我们的动画基本久已经完成了,在此我想补充一点,可能我们做到这之后运行起来可能会感觉View有时会有一种闪现的感觉,查找资料后,发现animation有个fillMode的属性我们设置未kCAFillModeForwards后能解决该问题
animation1.fillMode = kCAFillModeForwards
到此动画就算基本完成了附上代码