項(xiàng)目源代碼: Photo-Browser
一.傳遞數(shù)據(jù)
1.傳遞什么數(shù)據(jù)?
要在PhotoBrowserVc中查看大圖,首先要拿到圖片數(shù)據(jù)
2.怎么傳遞數(shù)據(jù)?直接傳遞圖片?
直接傳遞圖片url也可以,不過要從模型數(shù)組中抽離出來,不太好
最好的做法是:直接把模型數(shù)組傳遞給PhotoBrowserVc
二.自定義PhotoBrowserCell,用于展示數(shù)據(jù)
1.PhotoBrowserVc是UICollectionViewController,要想展示圖片,需要在cell上添加UIImageView
注意:如果一個(gè)構(gòu)造函數(shù)前有required,那么重寫了其他構(gòu)造函數(shù)時(shí),那么該構(gòu)造函數(shù)也必須被重寫
// MARK:- 重寫構(gòu)造函數(shù)
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
// required : 如果一個(gè)構(gòu)造函數(shù)前有required,那么重寫了其他構(gòu)造函數(shù)時(shí),那么該構(gòu)造函數(shù)也必須被重寫
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
2.要想展示圖片,需要設(shè)置什么?
2.1 設(shè)置UIImageView的image
直接從 SDWebImage緩存中取出原來cell的圖片(小圖)
注意:取出的圖片類型是可選類型,要先進(jìn)行判斷再使用
2.2 設(shè)置UIImageView的frame
2.21 根據(jù)取出的圖片的尺寸,計(jì)算圖片的frame(設(shè)置UIImageView的寬度等于屏幕寬度)
2.22 讓圖片的寬度等于UIImageView的寬度
2.23 UIImageView的高度,就等于 圖片高度 * UIImageView的寬度 / 圖片寬度 (讓圖片等寬高比拉伸)
3.加載高清圖片
3.1 為什么要加載高清圖片?
上面取出的圖片是小圖,不清晰. 查看大圖的時(shí)候,要換成高清圖片
3.2 怎么設(shè)置?
用SDWebImage加載大圖,把小圖設(shè)置為占位圖片
占位圖片:圖片還沒加載的時(shí)候,先用內(nèi)存中的一張圖片顯示到屏幕上,加載好圖片, 就顯示加載的圖片
4.設(shè)置完成后,查看大圖,發(fā)現(xiàn)滾動(dòng)到后面,發(fā)現(xiàn)圖片被壓縮了,為什么?
4.1 因?yàn)樵贛ainVc(首頁(yè))展示小圖的時(shí)候,給小圖也設(shè)置了占位圖片
4.2 在PhotoBrowserVc中查看大圖,滾動(dòng)到后面的時(shí)候,MainVc中的cell還沒顯示,小圖就不會(huì)被加載,就把占位圖片賦值給小圖
// 2.獲取小圖片
var smallImage = SDWebImageManager.sharedManager().imageCache.imageFromDiskCacheForKey(shop.q_pic_url)
if smallImage == nil {
smallImage = UIImage(named: "empty_picture")
}
4.3 這是UIImageView的尺寸就是根據(jù)占位圖片的尺寸計(jì)算出來的,跟實(shí)際圖片的尺寸會(huì)有差別,實(shí)際顯示的圖片就可能被壓縮
5.怎么解決圖片壓縮問題?
大圖請(qǐng)求成功時(shí),重新計(jì)算UIImageView的尺寸就可以了
三.把collectionView滾動(dòng)到正確的位置
1.為什么要滾動(dòng)collectionView?
PhotoBrowserVc的cell是從第0個(gè)cell開始顯示的, 所以每次點(diǎn)擊查看大圖都是從第0張圖片開始顯示
當(dāng)點(diǎn)擊MainVc(首頁(yè))cell的時(shí)候,要顯示對(duì)應(yīng)的大圖,不一定是第0張圖片
2.怎么滾動(dòng)?
2.1 在PhotoBrowserVc中定義indexPath屬性, 在MainVc中拿到cell的indexPath,對(duì) PhotoBrowserVc的indexPath賦值
2.2 滾動(dòng)到對(duì)應(yīng)的位置(用下面這個(gè)方法)
collectionView.scrollToItemAtIndexPath(indexPath!, atScrollPosition: .CenteredHorizontally, animated: false)
2.3 滾動(dòng)代碼應(yīng)該寫到哪里?
點(diǎn)擊MainVc的cell,就彈出查看大圖控制器(PhotoBrowserVc),就要滾動(dòng)要對(duì)應(yīng)的位置
所以,代碼可以寫到viewDidLoad里面
3. ??的使用
// ?? : 先判斷前面的可選鏈?zhǔn)欠裼兄? 如果有值,解包并且獲取對(duì)應(yīng)類型的值. 如果沒有值直接取后面的值
return shops?.count ?? 0
四.設(shè)置大圖之間的間距
1.怎么設(shè)置大圖之間的間距?用 minimumLineSpacing?
不可以,雖然能讓大圖之間有間距,但是會(huì)把后面的cell往后移 ,后面的cell就不能完全顯示在屏幕上
2.思考:可以collectionView的cell的寬度比屏幕寬度大一點(diǎn),多出來的寬度就當(dāng)做間距
不可行,collectionView(scrollView)的分頁(yè)效果,會(huì)讓用戶看到多出來的那部分
scrollView分頁(yè)效果的滾動(dòng)距離 是由scrollView的寬度來決定的
3.最終解決方案
3.1 只用一句代碼就可以搞定,把控制器的view的寬度增大一點(diǎn)就可以了
view.frame.size.with += 15
3.2 注意collectionView的寬度和 cell的寬度 要等于控制器的view的寬度才可以
4.全局函數(shù)的定義
4.1 什么是全局函數(shù)?
就是在工程目錄下的任何地方都能使用的函數(shù)
4.2 怎么定義全局函數(shù)?
只要把函數(shù)定義到AppDelegate里面就可以了
五.保存圖片
1.先要拿到對(duì)應(yīng)的圖片,根據(jù)indexPath拿?
點(diǎn)擊查看大圖后可能會(huì)被滾動(dòng),所以不能根據(jù)indexPath拿
2.怎么拿到正在顯示的圖片
2.1 先拿到正在顯示的cell
2.2 cell里面保存的就有image
3.怎么拿到cell
可以通過蘋果自帶的api拿到正在顯示的cell
// 1.1.拿到正在顯示的Cell
// visibleCells 返回所有在屏幕中顯示的Cell
let cell = collectionView.visibleCells()[0] as! PhotoBrowserViewCell
guard let image = cell.imageView.image else {
return
}
4.保存圖片到相冊(cè)
蘋果自帶api保存到相冊(cè)
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
六.點(diǎn)擊大圖關(guān)閉控制器
1.需求:點(diǎn)擊大圖或關(guān)閉按鈕,把控制器dismiss掉
2.點(diǎn)擊按鈕關(guān)閉
給按鈕設(shè)置點(diǎn)擊方法就可以了 addTarget
3.點(diǎn)擊大圖關(guān)閉怎么實(shí)現(xiàn)?
點(diǎn)擊大圖相當(dāng)于點(diǎn)擊了cell,在cell代理方法里面dismiss即可
七.自定義轉(zhuǎn)場(chǎng)(淡入淡出)
1.怎么自定義轉(zhuǎn)場(chǎng)動(dòng)畫?
遵守轉(zhuǎn)場(chǎng)的代理協(xié)議UIViewControllerTransitioningDelegate,實(shí)現(xiàn)代理方法
2.實(shí)現(xiàn)了代理方法,發(fā)現(xiàn)程序還是報(bào)錯(cuò),為什么?
代理方法都有一個(gè)返回值,返回值要遵守一個(gè)協(xié)議UIViewControllerContextTransitioning
才能作為返回值
3.在UIViewControllerContextTransitioning代理方法里面設(shè)置動(dòng)畫(具體看代碼)
4.設(shè)置消失動(dòng)畫
4.1 設(shè)置完顯示動(dòng)畫后,消失的時(shí)候也自動(dòng)會(huì)有一個(gè)動(dòng)畫效果,為什么?
因?yàn)?大圖view消失的時(shí)候,也是主控制器的view顯示的時(shí)候
看到的消失動(dòng)畫,實(shí)際上是主控制器的view的顯示動(dòng)畫
4.2 怎么判斷是顯示,還是消失?
定義一個(gè)屬性記錄即可
在UIViewControllerTransitioningDelegate代理方法中記錄
// MARK:- 遵守轉(zhuǎn)場(chǎng)的代理協(xié)議,和實(shí)現(xiàn)對(duì)應(yīng)的方法
extension HomeViewController : UIViewControllerTransitioningDelegate {
// 為彈出控制器做一個(gè)動(dòng)畫
func animationControllerForPresentedController(presented:
UIViewController, presentingController presenting:
UIViewController, sourceController source: UIViewController)
-> UIViewControllerAnimatedTransitioning? {
//記錄當(dāng)前為顯示階段
isPresented = true
return self
}
// 為消失控制器做一個(gè)動(dòng)畫
func animationControllerForDismissedController(dismissed:
UIViewController) -> UIViewControllerAnimatedTransitioning? {
//記錄當(dāng)前為消失階段
isPresented = false
return self
}
}
extension HomeViewController : UIViewControllerAnimatedTransitioning {
// 返回動(dòng)畫執(zhí)行的時(shí)間
func transitionDuration(transitionContext:
UIViewControllerContextTransitioning?)
-> NSTimeInterval {
return 3
}
// transitionContext : 轉(zhuǎn)場(chǎng)上下文
// 作用 : 可以通過上下文獲取到彈出的View和消失的View
// UITransitionContextFromViewKey : 獲取消失的View
// UITransitionContextToViewKey : 獲取彈出的View
func animateTransition(transitionContext:
UIViewControllerContextTransitioning) {
if isPresented {
// 獲取彈出的View
let presentedView = transitionContext.viewForKey(UITransitionContextToViewKey)!
//需要把view添加到父控件上,才能有動(dòng)畫效果
//父控件就是widow的containerView, 通過transitionContext.containerView()拿到
transitionContext.containerView()?.addSubview(presentedView)
// 修改View alpha值
presentedView.alpha = 0.0
// 執(zhí)行動(dòng)畫
UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
presentedView.alpha = 1.0
}) { (isFinished : Bool) in
//告訴控制器,轉(zhuǎn)場(chǎng)動(dòng)畫完成
transitionContext.completeTransition(isFinished)
}
} else {
// 1.獲取消失的View
let dismissedView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
// 2.執(zhí)行動(dòng)畫
UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
dismissedView.alpha = 0.0
}, completion: { (isFinished : Bool) in
//移除view,顯示主控制器的view
dismissedView.removeFromSuperview()
transitionContext.completeTransition(isFinished)
})
}
}
}
5.性能優(yōu)化,代碼抽取
5.1 把轉(zhuǎn)場(chǎng)動(dòng)畫的代理設(shè)置為主控制器,代理要全部寫在主控制器中,代碼臃腫,閱讀性差
5.2 怎么優(yōu)化?
把代理設(shè)置為其它對(duì)象,讓其它對(duì)象實(shí)現(xiàn)代理方法即可
5.3 具體實(shí)現(xiàn)步驟
5.31 創(chuàng)建一個(gè)對(duì)象(任何對(duì)象)
5.32 設(shè)置這個(gè)對(duì)象為轉(zhuǎn)場(chǎng)動(dòng)畫的代理
5.33 在對(duì)象中實(shí)現(xiàn)代理方法即可
5.34 注意:代理屬性為弱引用,要讓一個(gè)強(qiáng)引用指向它
八.最終動(dòng)畫效果
1.想要做動(dòng)畫,必須要拿到三個(gè)元素
1.1 圖片的起始位置(相對(duì)于控制器view的坐標(biāo)系)
1.2 圖片的終點(diǎn)位置(相對(duì)于控制器view的坐標(biāo)系)
1.3 轉(zhuǎn)場(chǎng)圖片的父控件UIImageView
2.彈出動(dòng)畫
2.1 獲取動(dòng)畫的三個(gè)元素
圖片的起始位置,終點(diǎn)位置和UIImageView 只有主控制器(mainVc)最清楚,可以定義代理
2.2 mainVc成為動(dòng)畫代理對(duì)象的代理,提供三個(gè)元素
3.消失動(dòng)畫
3.1 消失的時(shí)候,圖片的終點(diǎn)位置有可能發(fā)生變化,需要重新計(jì)算
3.2 怎么計(jì)算消失的圖片的終點(diǎn)位置?
只要拿到對(duì)應(yīng)cell的indexPath就可以計(jì)算位置
3.3 怎么拿到indexPath?
cell的indexPath只有PhotoBrowserVc最清楚,可以設(shè)置代理,讓PhotoBrowserVc提供indexPath
indexPath就是最后顯示在屏幕上cell的indexPath
// 1.獲取在屏幕中顯示的cell
let cell = collectionView.visibleCells()[0]
// 2.獲取cell對(duì)應(yīng)的indexPath
let indexPath = collectionView.indexPathForCell(cell)!
3.4 根據(jù)indexPath計(jì)算終點(diǎn)位置,完成動(dòng)畫
4.性能優(yōu)化
4.1 當(dāng)查看大圖滾動(dòng)的時(shí)候,indexPath會(huì)大于屏幕上顯示的indexPath最大值,這個(gè)時(shí)候,就獲取不到終點(diǎn)位置,就會(huì)沒有消失動(dòng)畫
4.2 怎么解決?
方法一: 當(dāng)超出的時(shí)候,給定一個(gè)終點(diǎn)位置,讓它在指定的位置消失
效果可以,但是滿足不了需求
4.3 最終方案(參考微信的解決方案)
當(dāng)獲取不到終點(diǎn)位置的時(shí)候,讓圖片消失的動(dòng)畫 設(shè)置為漸變消失動(dòng)畫
九.版本適配bug的解決
1.當(dāng)項(xiàng)目運(yùn)行到6s Plus上的時(shí)候,collectionView只能顯示兩列(需求是三列)
產(chǎn)生bug的原因是蘋果對(duì)臨界值得處理不太好
具體來說就是,屏幕寬度三等分,得到的數(shù)值是無限循環(huán)小數(shù),蘋果會(huì)根據(jù)數(shù)據(jù)類型對(duì)小數(shù)向前進(jìn)一位
這時(shí),屏幕的寬度就不足以放三個(gè)cell,就會(huì)把第三個(gè)cell擠到下一行顯示,就變成了兩列
2.bug解決
讓得到的cell的寬度減去一個(gè)臨界值小數(shù)即可(0.000001) 這個(gè)數(shù)值隨便寫
想了解更多請(qǐng)查看:輕松學(xué)習(xí)swift--swift項(xiàng)目初體驗(yàn)(一)
項(xiàng)目源代碼: Photo-Browser
喜歡就給個(gè)星星吧