func requestData (type : Int , urlString : String , parameters : [ String : NSObject] , callBack : (result : AnyObject? , error : NSErroe?) -> () ) func reqeustData(type : RequestType, urlString : String, parameters : [String : NSObject], finishedCallback : (result : AnyObject?, error : NSError?) -> ()) { }4.把方法裡面的閉包抽取出來 4.1 為什麼要抽取? 方法裡面閉包很長,代碼很亂,造成閱讀性差 4.2 怎麼抽取? 定義一個成員屬性 為閉包類型 把方法裡面的閉包,用屬性名 替換 五.項目集成工具類 把封裝好的工具類,直接拖到項目文件中 六.請求網絡數據 1.在控制器中調用工具類封裝好的網絡請求方法 2.解析數據 要對獲取到的數據進行類型轉換,應為從網絡加載的數據類型為AnyObject
guard let resultDict = result as? [String : NSObject] else { return } guard let dataArray = resultDict["data"] as? [[String : NSObject]] else { return }3.字典轉模型 3.1 創建模型 3.2 通過kvc手動轉模型 , 要重寫override func setValue(value: AnyObject?, forUndefinedKey key: String) {} 3.3 注意: 在閉包中 self. 也不可以省略 七,自定義cell,展示數據 1.創建cell繼承自UICollectionViewCell 2.在cell裡面定義模型屬性 3.監聽屬性改變(相當於oc的重寫set方法) 在屬性監聽器(willSet, didSet) 這裡用didSet方法裡面給模型裡面的屬性賦值 八.加載更多數據 1.什麼時候加載更多的數據? 當最後一個cell出現的時候 2.怎麼監聽最後一個cell是否出現在屏幕上 通過cell(item)的下標值(從0開始)是否等於數組長度 - 1
// 最後一個cell已經出現 if indexPath.item == shops.count - 1 { indexPath.item 相當於 tableView 的 indexPath.row loadHomeData(shops.count) }3.怎麼加載更多數據 和加載數據一樣,只不過多傳一個參數offset 九.彈出圖片浏覽器 1.創建圖片浏覽器的控制器對象UIViewController 2.彈出控制器 2.1 監聽cell的點擊 2.2 創建圖片浏覽器控制器對象 2.3 設置圖片浏覽器控制器對象的彈出樣式 photoBrowserVc.modalTransitionStyle = .FlipHorizontal 2.4 把控制器modal出來 十.布局圖片浏覽器 1.布局UICollectionView 1.1 創建UICollectionView 1.2 把UICollectionView添加到控制器的View上 1.3 設置數據源 1.4 自定義布局 2.布局兩個按鈕 2.1 創建兩個按鈕 2.2 設置按鈕的frame 2.3 對UIButton進行extension(擴展) 2.31 為什麼要進行擴展 創建出來的按鈕,要設置圖片,字體,和文字,一個個設置太麻煩,想讓按鈕創建出來就有這些屬性 2.32 怎麼進行擴展? 對UIButton進行extension(擴展) 擴充一個類型方法,在類方法裡面封裝好這些屬性
class func createBtn(title : String, bgColor : UIColor, fontSize : CGFloat) -> UIButton { let btn = UIButton() btn.backgroundColor = bgColor btn.setTitle(title, forState: .Normal) btn.titleLabel?.font = UIFont.systemFontOfSize(fontSize) return btn }2.4 這樣創建還不是很方便,我們可以給UIbutton擴展構造函數,創建的時候直接設置這些屬性 2.41 注意:在extension中擴充構造函數,只能擴充便利構造函數 2.42 什麼是便利構造函數? 1.必須在init前面加上convenience 2.必須在init方法中 調用self.init()
convenience init(title : String, bgColor : UIColor, fontSize : CGFloat) { self.init() setTitle(title, forState: .Normal) backgroundColor = bgColor titleLabel?.font = UIFont.systemFontOfSize(fontSize) }3.監聽按鈕的點擊 3.1 xcode7.2 和xcode7.3中監聽方法的寫法不太一樣 Xcode7.2 --> 1> Selector("方法的名稱") 2> "" Xcode7.3 --> #selector(類.方法名稱) 3.2 如果點擊按鈕調用的方法前面加上private 調用會報錯 3.21 為什麼會報錯 找不到方法 3.22 監聽事件實質就是發送一條消息 3.23 發送消息的過程是: 1.將消息包裝成@SEL 2.通過@SEL去類中的方法列表中找對相應的方法(函數) 3.34 在swift中,如果一個函數前面加上private,那麼該函數就不會被添加到消息(映射)列表中 3.35 如果在private前面加上@objc ,就會保留oc的特性, 該方法依然會添加到消息列表中 3.3 解決問題的方法就是 在private前面加上@objc 或者不寫private 十一.傳遞數據 1.傳遞什麼數據? 要在PhotoBrowserVc中查看大圖,首先要拿到圖片數據 2.怎麼傳遞數據?直接傳遞圖片? 直接傳遞圖片url也可以,不過要從模型數組中抽離出來,不太好 最好的做法是:直接把模型數組傳遞給PhotoBrowserVc 十二.自定義PhotoBrowserCell,用於展示數據 1.PhotoBrowserVc是UICollectionViewController,要想展示圖片,需要在cell上添加UIImageView 注意:如果一個構造函數前有required,那麼重寫了其他構造函數時,那麼該構造函數也必須被重寫
// MARK:- 重寫構造函數 override init(frame: CGRect) { super.init(frame: frame) setupUI() } // required : 如果一個構造函數前有required,那麼重寫了其他構造函數時,那麼該構造函數也必須被重寫 required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }2.要想展示圖片,需要設置什麼? 2.1 設置UIImageView的image 直接從 SDWebImage緩存中取出原來cell的圖片(小圖) 注意:取出的圖片類型是可選類型,要先進行判斷再使用 2.2 設置UIImageView的frame 2.21 根據取出的圖片的尺寸,計算圖片的frame(設置UIImageView的寬度等於屏幕寬度) 2.22 讓圖片的寬度等於UIImageView的寬度 2.23UIImageView的高度,就等於 圖片高度 *UIImageView的寬度 / 圖片寬度 (讓圖片等寬高比拉伸) 3.加載高清圖片 3.1 為什麼要加載高清圖片? 上面取出的圖片是小圖,不清晰. 查看大圖的時候,要換成高清圖片 3.2 怎麼設置? 用SDWebImage加載大圖,把小圖設置為占位圖片 占位圖片:圖片還沒加載的時候,先用內存中的一張圖片顯示到屏幕上,加載好圖片, 就顯示加載的圖片 4.設置完成後,查看大圖,發現滾動到後面,發現圖片被壓縮了,為什麼? 4.1 因為在MainVc(首頁)展示小圖的時候,給小圖也設置了占位圖片 4.2 在PhotoBrowserVc中查看大圖,滾動到後面的時候,MainVc中的cell還沒顯示,小圖就不會被加載,就把占位圖片賦值給小圖
// 2.獲取小圖片 var smallImage = SDWebImageManager.sharedManager().imageCache.imageFromDiskCacheForKey(shop.q_pic_url) if smallImage == nil { smallImage = UIImage(named: "empty_picture") }4.3 這是UIImageView的尺寸就是根據占位圖片的尺寸計算出來的,跟實際圖片的尺寸會有差別,實際顯示的圖片就可能被壓縮 5.怎麼解決圖片壓縮為題? 大圖請求成功時,重新計算UIImageView的尺寸就可以了 十三.把collectionView滾動到正確的位置 1.為什麼要滾動collectionView? PhotoBrowserVc的cell是從第0個cell開始顯示的, 所以每次點擊查看大圖都是從第0張圖片開始顯示 當點擊MainVc(首頁)cell的時候,要顯示對應的大圖,不一定是第0張圖片 2.怎麼滾動? 2.1 在PhotoBrowserVc中定義indexPath屬性, 在MainVc中拿到cell的indexPath,對PhotoBrowserVc的indexPath賦值 2.2 滾動到對應的位置(用下面這個方法)
collectionView.scrollToItemAtIndexPath(indexPath!, atScrollPosition: .CenteredHorizontally, animated: false)2.3 滾動代碼應該寫到哪裡? 點擊MainVc的cell,就彈出查看大圖控制器(PhotoBrowserVc),就要滾動要對應的位置 所以,代碼可以寫到viewDidLoad裡面 3. ??的使用
// ?? : 先判斷前面的可選鏈是否有值, 如果有值,解包並且獲取對應類型的值. 如果沒有值直接取後面的值 return shops?.count ?? 0十四.設置大圖之間的間距 1.怎麼設置大圖之間的間距?用 minimumLineSpacing? 不可以,雖然能讓大圖之間有間距,但是會把後面的cell往後移 ,後面的cell就不能完全顯示在屏幕上 2.思考:可以collectionView的cell的寬度比屏幕寬度大一點,多出來的寬度就當做間距 不可行,collectionView(scrollView)的分頁效果,會讓用戶看到多出來的那部分 scrollView分頁效果的滾動距離 是由scrollView的寬度來決定的 3.最終解決方案 3.1 只用一句代碼就可以搞定,把控制器的view的寬度增大一點就可以了 view.frame.size.with += 15 3.2 注意collectionView的寬度和 cell的寬度 要等於控制器的view的寬度才可以 4.全局函數的定義 4.1 什麼是全局函數? 就是在工程目錄下的任何地方都能使用的函數 4.2 怎麼定義全局函數? 只要把函數定義到AppDelegate裡面就可以了 十五.保存圖片 1.先要拿到對應的圖片,根據indexPath拿? 點擊查看大圖後可能會被滾動,所以不能根據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.保存圖片到相冊 蘋果自帶api保存到相冊 UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) 十六.點擊大圖關閉控制器 1.需求:點擊大圖或關閉按鈕,把控制器dismiss掉 2.點擊按鈕關閉 給按鈕設置點擊方法就可以了 addTarget 3.點擊大圖關閉怎麼實現? 點擊大圖相當於點擊了cell,在cell代理方法裡面dismiss即可 十七.自定義轉場(淡入淡出) 1.怎麼自定義轉場動畫? 遵守轉場的代理協議UIViewControllerTransitioningDelegate,實現代理方法 2.實現了代理方法,發現程序還是報錯,為什麼? 代理方法都有一個返回值,返回值要遵守一個協議UIViewControllerContextTransitioning才能作為返回值 3.在UIViewControllerContextTransitioning代理方法裡面設置動畫(具體看代碼) 4.設置消失動畫 4.1 設置完顯示動畫後,消失的時候也自動會有一個動畫效果,為什麼? 因為,大圖view消失的時候,也是主控制器的view顯示的時候 看到的消失動畫,實際上是主控制器的view的顯示動畫 4.2 怎麼判斷是顯示,還是消失? 定義一個屬性記錄即可 在UIViewControllerTransitioningDelegate代理方法中記錄
// MARK:- 遵守轉場的代理協議,和實現對應的方法 extension HomeViewController : UIViewControllerTransitioningDelegate { // 為彈出控制器做一個動畫 func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { //記錄當前為顯示階段 isPresented = true return self } // 為消失控制器做一個動畫 func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { //記錄當前為消失階段 isPresented = false return self } } extension HomeViewController : UIViewControllerAnimatedTransitioning { // 返回動畫執行的時間 func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 3 } // transitionContext : 轉場上下文 // 作用 : 可以通過上下文獲取到彈出的View和消失的View // UITransitionContextFromViewKey : 獲取消失的View // UITransitionContextToViewKey : 獲取彈出的View func animateTransition(transitionContext: UIViewControllerContextTransitioning) { if isPresented { // 獲取彈出的View let presentedView = transitionContext.viewForKey(UITransitionContextToViewKey)! //需要把view添加到父控件上,才能有動畫效果 //父控件就是widow的containerView, 通過transitionContext.containerView()拿到 transitionContext.containerView()?.addSubview(presentedView) // 修改View alpha值 presentedView.alpha = 0.0 // 執行動畫 UIView.animateWithDuration(transitionDuration(transitionContext), animations: { presentedView.alpha = 1.0 }) { (isFinished : Bool) in //告訴控制器,轉場動畫完成 transitionContext.completeTransition(isFinished) } } else { // 1.獲取消失的View let dismissedView = transitionContext.viewForKey(UITransitionContextFromViewKey)! // 2.執行動畫 UIView.animateWithDuration(transitionDuration(transitionContext), animations: { dismissedView.alpha = 0.0 }, completion: { (isFinished : Bool) in //移除view,顯示主控制器的view dismissedView.removeFromSuperview() transitionContext.completeTransition(isFinished) }) } } }
5.性能優化,代碼抽取 5.1 把轉場動畫的代理設置為主控制器,代理要全部寫在主控制器中,代碼臃腫,閱讀性差 5.2 怎麼優化? 把代理設置為其它對象,讓其它對象實現代理方法即可 5.3 具體實現步驟 5.31 創建一個對象(任何對象) 5.32 設置這個對象為轉場動畫的代理 5.33 在對象中實現代理方法即可 5.34 注意:代理屬性為弱引用,要讓一個強引用指向它 十八.最終動畫效果 1.想要做動畫,必須要拿到三個元素 1.1 圖片的起始位置(相對於控制器view的坐標系) 1.2 圖片的終點位置(相對於控制器view的坐標系) 1.3 轉場圖片的父控件UIImageView 2.彈出動畫 2.1 獲取動畫的三個元素 圖片的起始位置,終點位置和UIImageView 只有主控制器(mainVc)最清楚,可以定義代理 2.2 mainVc成為動畫代理對象的代理,提供三個元素 3.消失動畫 3.1 消失的時候,圖片的終點位置有可能發生變化,需要重新計算 3.2 怎麼計算消失的圖片的終點位置? 只要拿到對應cell的indexPath就可以計算位置 3.3 怎麼拿到indexPath? cell的indexPath只有PhotoBrowserVc最清楚,可以設置代理,讓PhotoBrowserVc提供indexPath indexPath就是最後顯示在屏幕上cell的indexPath // 1.獲取在屏幕中顯示的cell let cell = collectionView.visibleCells()[0] // 2.獲取cell對應的indexPath let indexPath = collectionView.indexPathForCell(cell)! 3.4 根據indexPath計算終點位置,完成動畫 4.性能優化 4.1 當查看大圖滾動的時候,indexPath會大於屏幕上顯示的indexPath最大值,這個時候,就獲取不到終點位置,就會沒有消失動畫(直接消失) 不滾動的時候消失的時候,圖片是不清晰的圖片 4.2 為什麼直接消失? 不滾動的時候,設置的圖片是小圖,所以不清晰 返回的時候獲取不到cell,獲取不到cell就直接返回空的ImageView ImageView還沒有設置圖片就直接返回了 4.3 怎麼解決? 在PhotoBrowserVc中可以拿到高清圖片 設置代理拿到Image,消失動畫的時候,直接顯示高清圖片 4.4 消失的時候,發現還是直接消失為什麼? 因為滾動到後面,mainVc的cell不在屏幕上,就獲取不到cell, 所以消失時獲取的startRect = CGRectZero 從0消失到0 所以沒有動畫 4.5 點擊mainVc最後一個cell,看看大圖,往後滾,返回的時候,發現消失動畫最終消失到左上角為什麼? 因為,後面的cell還沒出現,就獲取不到最終位置,系統默認在左上角 4.6 怎麼解決? 方法一: 當超出的時候,給定一個終點位置,讓它在指定的位置消失 效果可以,但是滿足不了需求 4.7 最終方案(參考微信的解決方案) 當獲取不到終點位置的時候,讓圖片消失的動畫 設置為漸變消失動畫 十九.版本適配bug的解決 1.當項目運行到6s Plus上的時候,collectionView只能顯示兩列(需求是三列) 產生bug的原因是蘋果對臨界值得處理不太好 具體來說就是,屏幕寬度三等分,得到的數值是無限循環小數,蘋果會根據數據類型對小數向前進一位 這時,屏幕的寬度就不足以放三個cell,就會把第三個cell擠到下一行顯示,就變成了兩列 2.bug解決 讓得到的cell的寬度減去一個臨界值小數即可(0.000001) 這個數值隨便寫