swift 模仿 美麗說App
一 總體實現功能圖
1 圖一 : 系統自帶屬性完成動畫翻轉
2 圖二 : 自定義動畫實現翻轉
3 應用圖標;啟動圖片;app名字…這些我就不一 一介紹了,你們都應該知道怎么配置吧.我這里是由于錄的比較短,所以這部分內容省略了.這里說下幾點注意.
—-> 3.1 修改app名字可以在Bundle name中修改,也可以在Bundle display name中修改.
—-> 3.2 當我們向Brand Assets中設置啟動圖片的時候,很有可能往里面扔的圖片處占滿了幾個格子,但是還是有少部分格子并沒有圖片,這時會有警告出現,我們在開發中要盡可能的減少不必要的警告,當然第三方框架是可以除外的,如果要消除警告,那還得和作者商量才行,比較麻煩.沒有用的格子會報警告,我們直接刪除就可以了.
二 demo的總體架構
1 通過功能圖不難看出,是一個UICollectionViewController和一個NavigationController控制器構成.我這里直接采用UICollectionViewController作為NavigationController的子控制器.
2 創建文件,刪除自帶控制器,拖入一個UICollectionViewController控制器.然后按下面圖片展示插入一個NavigationController控制器.將NavigationController設置為箭頭指向的控制器.
3 其余部分都用代碼實現
三 封裝網絡請求工具類
1 創建swift文件
2 將網絡請求工具類對象設置成單例(外界創建的都是同一個對象)
class NetworkTools: AFHTTPSessionManager {
//1.將NetworkTools設置成單例
static let shareIntance : NetworkTools = {
let tools = NetworkTools()
//加上這句能增加解析的種類
tools.responseSerializer.acceptableContentTypes?.insert("text/html")
return tools
}()
}
3 定義枚舉 : 定義請求方式的枚舉,用來作為參數傳遞,作為判斷選擇哪種方式進行請求
// Mark: - 定義枚舉,用來作為傳值的參數
enum RequestType {
case GET
case POST
}
4 封裝網絡請求 : 由于我們寫這部分的代碼可能比較多,那么我們考慮通過給類擴展一些方法,在該方法中實現網絡請求的封裝
// MARK: - 封裝網絡請求
extension NetworkTools {
//注意需要傳入的參數:請求的方式;url;需要拼接的參數;成功后的回調(閉包)
func request(requestType: RequestType, urlString: String, parameters: [String : AnyObject], finished: (result : AnyObject?, error : NSError?) ->()) { //定義成功后的閉包 let successCallBack = {(task : NSURLSessionDataTask, result : AnyObject?) -> Void in finished(result: result, error: nil)
}
//定義失敗后的閉包
let failureCallBack = {(task : NSURLSessionDataTask?, error : NSError) -> Void in finished(result: nil, error: error)
}
//根據傳入的參數判斷選擇哪種請求方式
if requestType == .GET {
GET(urlString, parameters: parameters, progress: nil, success: successCallBack, failure: failureCallBack)
}else{
POST(urlString, parameters: parameters, progress: nil, success: successCallBack, failure: failureCallBack)
}
}
}
5 解析 : 成功后的閉包和失敗后的閉包,都是通過回調的方式,類似于OC中的block,回調的作用.
6 請求首頁數據
—-> 6.1 由于第4點已經封裝了請求方式,那么這部分內容就是對url,參數,發送請求的處理.注意需要做出判斷,才能讓代碼更嚴謹.
//請求首頁數據
extension NetworkTools {
//需要有回調后的參數(閉包)
func loadHomeData (offSet : Int, finished : (result : [[String : AnyObject]]?, error : NSError?) -> ()) {
//1. 獲取url
let urlString = "http://mobapi.meilishuo.com/2.0/推ter/popular.json"
//2. 拼接請求參數
let parameters = [
"offset" : "\(offSet)",
"limit" : "30",
"access_token" : "b92e0c6fd3ca919d3e7547d446d9a8c2"
]
//3. 發送請求
request(.GET, urlString: urlString, parameters: parameters) { (result, error) -> () in
// 3.1 判斷請求是否出錯
if error != nil {
finished(result: nil, error: error)
}
//3.2 獲取結果(將可選類型的結果轉為具體的結果)
guard let result = result as? [String : AnyObject] else {
finished(result: nil, error: NSError(domain: "data error", code: -1011, userInfo: nil))
return
}
//3.3 將結果回調
finished(result: result["data"] as? [[String : AnyObject]], error: nil)
}
}
}
7 外面調用方法 : 通過拿到這個方法中的單例,然后調用loadHomeData函數就能達到數據請求的目的了.
四 模型
1 創建模型 :
2 根據我們自己的需求,將請求出來的數據,進行在線格式化,然后抽取我們自己需要的模型數據屬性.我們做的demo只需要小圖和大圖就可以.
class ShopItem: NSObject {
//模型中需要用到的屬性
var q_pic_url = ""
var z_pic_url = ""
//字典轉模型
init(dict : [String : AnyObject]) {
super.init()
//KVC
setValuesForKeysWithDictionary(dict)
}
//重寫系統由于模型中屬性不是一一對應會報錯的函數(重寫函數中什么都不做,就不會報錯)
override func setValue(value: AnyObject?, forUndefinedKey key: String) {
}
}
—-> 2.1 我們直接用KVC的方式直接給模型的屬性賦值,不采取MJ的框架了.但是會報錯,因為屬性并不是一 一對應,所以需要我們將系統報錯的方法重寫.只要重寫就不會報錯了.
五 數據源方法
1 由于我們子控制器就是采用UICollectionViewController,所以就不需要設置collectionView了.
2 這部分數據源代碼可能也會很多,所以這里也采用給類擴展一個方法,在這個方法中寫數據源方法
//MARK: - 給類擴充方法
extension HomeViewController {
//cell的個數
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//根據模型中的數據才能知道有多少個cell
return self.shops.count
}
//cell的內容
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
//注冊
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("HomeCell", forIndexPath: indexPath) as! HomeViewCell
//取出item
cell.shops = shops[indexPath.item]
//判斷是否是最后一個出現(如果是最后一個,那么久再次請求數據)
if indexPath.item == self.shops.count - 1 {
loadData(self.shops.count)
}
return cell
}
//點擊cell[彈出控制器
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
showPhotoBrowser(indexPath)
}
}
—-> 2.1 解析 : 里面包括cell的個數(取決于模型);cell的內容(取決于模型);判斷最后一個cell出現,就調用數據請求的方法
六 自定義流水布局
1 定義一個繼承于UICollectionViewFlowLayout的流水布局
2 流水布局的代碼
class HomeCollectionViewFlowLayout: UICollectionViewFlowLayout {
override func prepareLayout() {
super.prepareLayout()
//列數
let cols : CGFloat = 3
//間距
let margin : CGFloat = 10
//寬度和高度
let itemWH = (UIScreen.mainScreen().bounds.width - (cols + 1) * margin) / cols
//設置布局內容
itemSize = CGSize(width: itemWH, height: itemWH)
minimumInteritemSpacing = margin
minimumLineSpacing = margin
//設置內邊距
collectionView?.contentInset = UIEdgeInsets(top: margin + 64, left: margin, bottom: margin, right: margin)
}
}</code></pre>
—-> 2.1 解析 : 注意需要設置上下左右的內邊距還有需要進行下面圖片的配置才能出現圖片

—-> 2.2 需要將系統的流水布局設置為自定義的才能達到效果
七 自定義cell
1 這個相比大家都知道,對于collectionView是必須自定義cell的,不要問我為什么,這是規定.
2 拖入個UIImageView控件到cell中,然后自動布局好約束,通過拖線的方式拿到UIImageView對象

3 自定義cell中的代碼
import UIKit
import SDWebImage
class HomeViewCell: UICollectionViewCell {
@IBOutlet weak var imageView: UIImageView!
//didSet方法
var shops : ShopItem? {
didSet {
//1. 校驗
guard let urlString = shops?.z_pic_url else {
return
}
//2.創建url
let url = NSURL(string: urlString)
//3.設置圖片
imageView.sd_setImageWithURL(url, placeholderImage: UIImage(named: "empty_picture"))
}
}
}</code></pre>
八 調用方法,發送網絡請求
1 在主控制器中調用方法來發送網絡請求
class HomeViewController: UICollectionViewController {
//懶加載裝模型的數組
private lazy var shops : [ShopItem] = [ShopItem]()
private lazy var photoBrowserAnimator = PhotoBrowserAnimator()
override func viewDidLoad() {
super.viewDidLoad()
//調用網絡請求函數
loadData(0)
}
}
2 網絡數據請求
//MARK: - 發送網絡請求
extension HomeViewController {
//加載數據
func loadData(offSet : Int) {
NetworkTools.shareIntance.loadHomeData(offSet) { (result, error) -> () in
//1. 錯誤校驗
if error != nil {
//打印錯誤信息
print(error)
return
}
//2. 獲取結果
guard let resultArray = result else {
print("獲取的結果不正確")
return
}
//3. 遍歷所有的結果
for resultDict in resultArray {
// print(resultDict)
let shop : ShopItem = ShopItem(dict : resultDict)
self.shops.append(shop)
}
//4.刷新表格
self.collectionView?.reloadData()
}
}
}
九 cell的點擊
1 實現collectionView數據源中的一個方法
//點擊cell彈出控制器
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
showPhotoBrowser(indexPath)
}
2 彈出控制器
//Mark: - 點擊圖片,彈出控制器
extension HomeViewController {
//定義為私有的
private func showPhotoBrowser( indexPath : NSIndexPath) {
//創建控制器對象
let showPhotoBrowserVC = PhotoBrowserController()
//傳入點擊的cell的角標
showPhotoBrowserVC.indexPath = indexPath
//讓model出來的控制器和源控制器中模型數據相等
showPhotoBrowserVC.shopItem = shops
showPhotoBrowserVC.view.backgroundColor = UIColor.redColor()
//設置model出控制器的模式
showPhotoBrowserVC.modalPresentationStyle = .Custom
// showPhotoBrowserVC.modalTransitionStyle = .PartialCurl
//設置動畫代理
showPhotoBrowserVC.transitioningDelegate = photoBrowserAnimator
//model出控制器
presentViewController(showPhotoBrowserVC, animated: true, completion: nil)
}
}
十 展示點擊圖片的控制器
1 創建文件

2 由總體的功能圖我們可以看出,view是支持左右滑動的,并不支持上下滑動.所以我們可以通過創建一個普通的UIViewController,在上面加上一個UICollectionView就可以了.
3 創建一個類,用來創建按鈕的時候,直接使用里面的方法

4 便利構造函數(我前面有介紹)
//創建類
extension UIButton {
//便利構造函數
//函數前面必須加上convenience關鍵字
convenience init(title : String, bgColor : UIColor, fontSize : CGFloat) {
self.init()
backgroundColor = bgColor
setTitle(title, forState: .Normal)
titleLabel?.font = UIFont.systemFontOfSize(fontSize)
}
}
—-> 4.1 解析 : 外面直接UIButton()的時候就會出現這樣一個方法,在括號中填入相應的參數就可以創建按鈕了.
5 UICollectionView中的相關設置
class PhotoBrowserController: UIViewController {
//定義屬性
let PhoptoBrowserCell = "PhoptoBrowserCell"
var shopItem : [ShopItem]?
var indexPath : NSIndexPath?
//1.懶加載(model出來的控制器;保存按鈕;取消按鈕)
lazy var collectionView : UICollectionView = UICollectionView(frame: CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height), collectionViewLayout: PhotoBrowerFlowLayout())
lazy var colseBtn : UIButton = UIButton(title: "關 閉", bgColor:UIColor.darkGrayColor() , fontSize: 16.0)
lazy var saveBtn : UIButton = UIButton(title: "保 存", bgColor: UIColor.darkGrayColor(), fontSize: 16.0)
override func viewDidLoad() {
super.viewDidLoad()
//添加UI控件
setUpUI()
//滾到對應的位置
collectionView.scrollToItemAtIndexPath(indexPath!, atScrollPosition: .Left, animated: false)
}
6 添加并且布局子控件的位置
//Mark: - 添加UI控件
extension PhotoBrowserController {
func setUpUI() {
//1. 添加控件到view中
view.addSubview(collectionView)
view.addSubview(colseBtn)
view.addSubview(saveBtn)
//2. 設置子控件的frame
//2.1 設置collectionView
collectionView.frame = view.bounds
//2.2 設置關閉按鈕
let colseBtnX : CGFloat = 20.0
let colseBtnW : CGFloat = 90.0
let colseBtnH : CGFloat = 32.0
let colseBtnY : CGFloat = view.bounds.height - colseBtnH - 20.0
colseBtn.frame = CGRectMake(colseBtnX, colseBtnY, colseBtnW, colseBtnH)
//2.3 設置保存按鈕
let saveBtnX = view.bounds.width - colseBtnW - 20
saveBtn.frame = CGRectMake(saveBtnX, colseBtnY, colseBtnW, colseBtnH)
//監聽按鈕的點擊
colseBtn.addTarget(self, action: "closeBtnClick", forControlEvents: .TouchUpInside)
saveBtn.addTarget(self, action: "saveBtnClick", forControlEvents: .TouchUpInside)
//注冊
collectionView.registerClass(PhotoBrowerViewCell.self, forCellWithReuseIdentifier: PhoptoBrowserCell)
//設置數據源
collectionView.dataSource = self
//設置代理
collectionView.delegate = self
}
}</code></pre>
7 數據源方法
extension PhotoBrowserController : UICollectionViewDataSource,UICollectionViewDelegate {
//MARK: - cell的個數
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return shopItem?.count ?? 0
}
//MARK: - cell的內容
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(PhoptoBrowserCell, forIndexPath: indexPath) as! PhotoBrowerViewCell
cell.shopItem = shopItem?[indexPath.item]
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
//調用closeBtnClick方法
closeBtnClick()
}
}</code></pre>
8 監聽關閉和保存按鈕的點擊
extension PhotoBrowserController {
@objc private func closeBtnClick () {
// print("closeBtnClick")
//dismiss控制器
dismissViewControllerAnimated(true, completion: nil)
}
@objc private func saveBtnClick() {
// print("saveBtnClick")
//取出在屏幕中顯示的cell
let cell = collectionView.visibleCells().first as! PhotoBrowerViewCell
//取出圖片(該方法中的imageView不能直接拿來用,因為設置了為私有的)
let image = cell.imageView.image
//保存圖片到相冊
UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
}
}
9 自定義流水布局(和前面基本一樣)
class PhotoBrowerFlowLayout: UICollectionViewFlowLayout {
//設置流水布局相關屬性
override func prepareLayout() {
super.prepareLayout()
itemSize = (collectionView?.bounds.size)!
minimumInteritemSpacing = 0
minimumLineSpacing = 0
scrollDirection = .Horizontal
//設置分頁
collectionView?.pagingEnabled = true
collectionView?.showsHorizontalScrollIndicator = false
}
}</code></pre>
10 自定義cell
—-> 10.1 需要考慮的因素 : 1 >返回的url是否存在? 2> 能不能直接沖緩存池中獲取?
—-> 10.2 代碼
class PhotoBrowerViewCell: UICollectionViewCell {
//懶加載顯示圖片的控件
lazy var imageView : UIImageView = UIImageView()
//提供加載圖片的模型的set方法
var shopItem : ShopItem? {
didSet {
//1.校驗小圖片的url是否為空
guard let urlString = shopItem?.q_pic_url else {
return
}
//2. 去Cach中獲取小圖
var smallImage = SDWebImageManager.sharedManager().imageCache.imageFromMemoryCacheForKey(urlString)
//3.如果獲取不到圖片就賦值為占位圖片
if smallImage == nil {
smallImage = UIImage(named: "empty_picture")
}
//4. 計算展示的圖片的frame
imageView.frame = calculate(smallImage)
//5. 獲取大圖片的url是否為空
guard let bigUrlString = shopItem?.z_pic_url else {
return
}
//6 .創建大圖的url
let bigUrl = NSURL(string: bigUrlString)
//7. 加載大圖
imageView.sd_setImageWithURL(bigUrl, placeholderImage: smallImage, options: .RetryFailed) {(image, error, type, url) -> Void in
self.imageView.frame = self.calculate(image)
}
}
}
// MARK: - 構造函數
override init(frame: CGRect) {
super.init(frame: frame)
setUpUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}</code></pre>
—-> 10.3 解析 : 下面這對代碼是成對出現的
// MARK: - 構造函數
override init(frame: CGRect) {
super.init(frame: frame)
setUpUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}</code></pre>
11 設置加載后圖片的擺放位置
//MARK: - 設置圖片的frame
extension PhotoBrowerViewCell {
private func calculate (image : UIImage) ->CGRect {
let imageViewX : CGFloat = 0.0
let imageViewW : CGFloat = UIScreen.mainScreen().bounds.width
let imageViewH : CGFloat = imageViewW / image.size.width * image.size.height
let imageViewY : CGFloat = (UIScreen.mainScreen().bounds.height - imageViewH) * 0.5
return CGRect(x: imageViewX, y: imageViewY, width: imageViewW, height: imageViewH)
}
}
12 添加imageView
//MARK: - 添加子控件
extension PhotoBrowerViewCell {
private func setUpUI() {
contentView.addSubview(imageView)
}
}
十一 第一種轉場動畫
1 直接采用蘋果推薦的方法用一個屬性就可以直接搞定modalTransitionStyle
showPhotoBrowserVC.modalTransitionStyle = .PartialCurl
十二 自定義轉場動畫
1 創建轉場動畫類

2 設置該類為動畫的代理
showPhotoBrowserVC.transitioningDelegate = photoBrowserAnimator
3 定義一個屬性 : 作為判斷彈出動畫還是消失動畫的條件
class PhotoBrowserAnimator: NSObject {
var isPresented : Bool = false
}
4 實現代理方法
//MARK: - 實現轉場動畫的代理方法
extension PhotoBrowserAnimator : 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
}
}
5 自定義(通過改變透明度實現)
extension PhotoBrowserAnimator : UIViewControllerAnimatedTransitioning {
//返回動畫執行的時間
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 2.0
}
//獲取轉場的上下文:可以通過上下文獲取到執行動畫的view
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
isPresented ? animateForPresentView(transitionContext) : animateForDismissed(transitionContext)
}
—-> 5.1 彈出動畫
//彈出
func animateForPresentView(transitionContext: UIViewControllerContextTransitioning) {
//1 取出彈出的view
let presentView = transitionContext.viewForKey(UITransitionContextToViewKey)!
//2 將彈出的view加入到contentView中
transitionContext.containerView()?.addSubview(presentView)
//3 執行動畫
presentView.alpha = 0.0
UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void in
presentView.alpha = 1.0
}) { (_) -> Void in
//完成動畫
transitionContext.completeTransition(true)
}
}
—-> 5.2 消失動畫
//消失
func animateForDismissed (transitionContext: UIViewControllerContextTransitioning) {
//1 取出消失的view
let dismissView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
//2 執行動畫
UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void in
dismissView.alpha = 0.0
}) { (_) -> Void in
//移除view
dismissView.removeFromSuperview()
//完成動畫
transitionContext.completeTransition(true)
}
}
十三 細節
每個cell之間應該存在距離,但是直接通過設置: minimumInteritemSpacing和minimumLineSpacing兩個屬性是無法達到效果的.那么我這里有一種方法,直接collectionView的寬度增加一點,但是圖片的寬度還是屏幕那么大,那么用戶是看不到超出屏幕的view的,用下面代碼就可以搞定.
//設置cell之間的間隔(這種方法是通過將collectionView的寬度增加來實現的)
override func loadView() {
super.loadView()
view.frame.size.width += 15
}
十四 總結
這部分美麗說demo還不是很完善,還有一個動畫翻轉的方式還沒有實現,那中方式是一種比較難的方式,但是達到的效果很好,后續我還是會補上的.最后,麻煩大家多多關注我的博客,謝謝!!!!
來自: http://blog.csdn.net/xf931456371/article/details/51278805