主要內(nèi)容:實(shí)現(xiàn)自定義轉(zhuǎn)場動畫
大家平常用微信,微博的過程中肯定都有查看過朋友圈和微博所發(fā)布的照片,當(dāng)點(diǎn)擊九宮格的某一圖片時(shí)圖片會慢慢的放大并進(jìn)入全屏,左右滑動查看另一張.輕點(diǎn)圖片又會以動畫的方式慢慢縮小回到滑動之后對應(yīng)的圖片.直接上圖和代碼吧,talk is cheap.
- 先展示一下效果
![gif1](https://coding.net/u/DHai/p/PhotoViewer-Swift/git/raw/master/photo1.gif)
![gif2](https://coding.net/u/DHai/p/PhotoViewer-Swift/git/raw/master/photo2.gif)
-
具體實(shí)現(xiàn)步驟
- 1 .創(chuàng)建兩個(gè)控制器,一個(gè)是
HomeViewController
(modal之前的控制器),九宮格展示圖片列表,另一個(gè)是PhotoViewController
(之后需要modal出來的控制器),用于展示大圖.
- 2 .自定義一個(gè)類(可以定義成工具類,方便復(fù)用),需要繼承自
NSObject
并遵守UIViewControllerTransitioningDelegate
協(xié)議,并且實(shí)現(xiàn)該協(xié)議中的兩個(gè)代理方法,目的是為了設(shè)置管理顯示和消失動畫是由哪個(gè)控制器管理,isPresented
屬性用于之后的判斷是消失動畫還是顯示動畫.
class TransionAnimator: NSObject { // MARK:- 設(shè)置屬性 var isPresented : Bool = false var indexPath : NSIndexPath?
- 1 .創(chuàng)建兩個(gè)控制器,一個(gè)是
}
// 設(shè)置管理出現(xiàn)動畫
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning?{
isPresented = true
return self
}
// 設(shè)置管理消失動畫
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
isPresented = false
return self
}
```
- 3 .在`HomeViewController`中去定義一個(gè)屬性用來保存轉(zhuǎn)場動畫管理者,因?yàn)槿绻搶ο蟊会尵蜔o法完成轉(zhuǎn)場動畫.并且將這個(gè)對象設(shè)置成為`PhotoViewController`的`transitioningDelegate`,并且實(shí)現(xiàn)`UIViewControllerAnimatedTransitioning`協(xié)議中的方法,才能自定義轉(zhuǎn)場動畫
-
4 .來到自定義的動畫管理者文件中,實(shí)現(xiàn)協(xié)議中的方法
//設(shè)置動畫持續(xù)時(shí)間 public func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval //設(shè)置具體的動畫 public func animateTransition(transitionContext: UIViewControllerContextTransitioning)
-
5 如何去實(shí)現(xiàn)動畫:
- 5.1 考慮到完成顯示和消失的動畫需要三個(gè)值,起始的位置,結(jié)束的位置,還有執(zhí)行動畫的占位視圖.但是這三個(gè)值并不是固定的,需要外界傳入.這時(shí)就考慮面向協(xié)議開發(fā),就像許多第三方框架所做的:只需要調(diào)用提供的接口,并傳入所需要的接口,
// 顯示動畫代理
protocol TransitionPresentedProtocol : class {
func getImageView(indexPath : NSIndexPath) -> UIImageView
func getStartRect(indexPath : NSIndexPath) -> CGRect
func getEndRect(indexPath : NSIndexPath) -> CGRect
}
//消失動畫代理
protocol TransitionDismissProtocol : class {
func getImageView() -> UIImageView
func getIndexPath() -> NSIndexPath
}
```
- 5.2 實(shí)現(xiàn)代理方法,獲得需要的值:
```swift
//實(shí)現(xiàn)顯示動畫代理,獲得返回值
extension HomeViewController : TransitionPresentedProtocol {
func getImageView(indexPath: NSIndexPath) -> UIImageView {
let imageView = UIImageView()
let cell = collectionView(collectionView!, cellForItemAtIndexPath: indexPath) as! HomeViewCell
imageView.image = cell.imageView.image
imageView.contentMode = .ScaleAspectFill
imageView.clipsToBounds = true
return imageView
}
//
func getEndRect(indexPath: NSIndexPath) -> CGRect {
let cell = collectionView(collectionView!, cellForItemAtIndexPath: indexPath) as! HomeViewCell
return calculateImageFrame(cell.imageView.image!)
}
func getStartRect(indexPath: NSIndexPath) -> CGRect {
let cell = collectionView(collectionView!, cellForItemAtIndexPath: indexPath) as! HomeViewCell
let startRect = collectionView!.convertRect(cell.frame, toView: UIApplication.sharedApplication().keyWindow)
return startRect
}
```
```swift
//實(shí)現(xiàn)消失動畫代理,獲得返回值
extension PhotoViewController : TransitionDismissProtocol {
func getImageView() -> UIImageView {
let cell = collectionV.visibleCells().first as! PhotoViewCell
let imageView = cell.imageV
imageView.contentMode = .ScaleAspectFill
imageView.clipsToBounds = true
return imageView
}
func getIndexPath() -> NSIndexPath {
let cell = collectionV.visibleCells().first as! PhotoViewCell
let indexPath = collectionV.indexPathForCell(cell)
return indexPath!
}
```
- 5.3 實(shí)現(xiàn)動畫效果
```swift
//設(shè)置轉(zhuǎn)場動畫時(shí)長
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 2.0
}
//設(shè)置轉(zhuǎn)場動畫
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
//設(shè)置顯示動畫
if isPresented {
//0.nil值校驗(yàn)
guard let indexPath = indexPath,presentedDelegate = presentedDelegate else {return}
//1獲得彈出的View
let presentedView = transitionContext.viewForKey(UITransitionContextToViewKey)!
//2.獲得占位視圖
let imageV = presentedDelegate.getImageView(indexPath)
//3.將占位視圖添加到containerView上
transitionContext.containerView()?.addSubview(imageV)
//4.設(shè)置占位視圖的frame
imageV.frame = presentedDelegate.getStartRect(indexPath)
//5.容器視圖設(shè)置背景色
transitionContext.containerView()?.backgroundColor = UIColor.blackColor()
//6.執(zhí)行動畫
UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void in
imageV.frame = presentedDelegate.getEndRect(indexPath)
}, completion: { (_) -> Void in
//移除占位視圖
imageV.removeFromSuperview()
//將彈出的View加到containerView上
transitionContext.containerView()?.addSubview(presentedView)
//容器視圖清除背景色
transitionContext.containerView()?.backgroundColor = UIColor.clearColor()
//設(shè)置已完成轉(zhuǎn)場
transitionContext.completeTransition(true)
})
} else {//設(shè)置消失動畫
//0.nil值校驗(yàn)
guard let presentedDelegate = presentedDelegate , dismissDelegate = dismissDelegate else {return}
//1獲得即將消失的view
let dismissView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
//3.獲得占位imageView
let imageView = dismissDelegate.getImageView()
//4.將占位視圖添加到容器視圖上面
transitionContext.containerView()?.addSubview(imageView)
//2執(zhí)行消失動畫
//獲得最終的indexPath
let indexPath = dismissDelegate.getIndexPath()
//獲得之前顯示動畫的起始位置,
var endRect = presentedDelegate.getStartRect(indexPath)
//判斷最終消失的位置是否在屏幕之外
if endRect.origin.y > UIScreen.mainScreen().bounds.height {
endRect = CGRectZero
}
dismissView.alpha = endRect == CGRectZero ? 1.0 : 0.0
imageView.alpha = 1.0 - dismissView.alpha
UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void in
if endRect == CGRectZero {
dismissView.alpha = 0.0
} else {
imageView.frame = endRect
}
}, completion: { (_) -> Void in
imageView.removeFromSuperview()
dismissView.removeFromSuperview()
transitionContext.completeTransition(true)
})
}
}
```
- 6 注意事項(xiàng):
- 如果在PhotoView里一直向后滑動,那么返回之前的HomeView的時(shí)候可能會導(dǎo)致崩潰.這是由于cell的重用機(jī)制,只有當(dāng)cell即將顯示出來的時(shí)候才會去緩存池中取cell并加載,若HomeView中的cell超出了屏幕的范圍,是取不出cell的,從而導(dǎo)致崩潰.那么解決方法可以時(shí)主動去調(diào)用數(shù)據(jù)源方法,傳入indexPath返回一個(gè)cell,再去使用cell里面的數(shù)據(jù),或者使用設(shè)置offset的方式,滾動到對應(yīng)位置,就能取出對應(yīng)的cell了. 我使用的是主動去調(diào)用
-
![Uploading photo1_989090.gif . . .]transitionContext.containerView
,也就是容器層是用來展示最后modal出來的View,要把占位視圖和最后modal出來的View添加上去
代碼地址
下載之后pod install,打開.xcworkspace就能運(yùn)行
https://git.coding.net/DHai/PhotoViewer-Swift.git