一 总体实现功能图(由于csdn上传的视频有规定大小,这里只简短的录了点,抱歉)

1 图一 : 系统自带属性完成动画翻转

美丽说demo(纯swift代码)-编程知识网

2 图二 : 自定义动画实现翻转

美丽说demo(纯swift代码)-编程知识网

3 应用图标;启动图片;app名字…这些我就不一 一介绍了,你们都应该知道怎么配置吧.我这里是由于录的比较短,所以这部分内容省略了.这里说下几点注意.

—-> 3.1 修改app名字可以在Bundle name中修改,也可以在Bundle display name中修改.
—-> 3.2 当我们向Brand Assets中设置启动图片的时候,很有可能往里面扔的图片处占满了几个格子,但是还是有少部分格子并没有图片,这时会有警告出现,我们在开发中要尽可能的减少不必要的警告,当然第三方框架是可以除外的,如果要消除警告,那还得和作者商量才行,比较麻烦.没有用的格子会报警告,我们直接删除就可以了.

demo的总体架构

1 通过功能图不难看出,是一个UICollectionViewController和一个NavigationController控制器构成.我这里直接采用UICollectionViewController作为NavigationController的子控制器.

2 创建文件,删除自带控制器,拖入一个UICollectionViewController控制器.然后按下面图片展示插入一个NavigationController控制器.将NavigationController设置为箭头指向的控制器.

美丽说demo(纯swift代码)-编程知识网

3 其余部分都用代码实现

三 封装网络请求工具类

1 创建swift文件

美丽说demo(纯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 GETcase 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. 获取urllet urlString = "http://mobapi.meilishuo.com/2.0/twitter/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 创建模型 :

美丽说demo(纯swift代码)-编程知识网

2 根据我们自己的需求,将请求出来的数据,进行在线格式化,然后抽取我们自己需要的模型数据属性.我们做的demo只需要小图和大图就可以.

class ShopItem: NSObject {//模型中需要用到的属性var q_pic_url = ""var z_pic_url = ""//字典转模型init(dict : [String : AnyObject]) {super.init()//KVCsetValuesForKeysWithDictionary(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 {//根据模型中的数据才能知道有多少个cellreturn self.shops.count}//cell的内容override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {//注册let cell = collectionView.dequeueReusableCellWithReuseIdentifier("HomeCell", forIndexPath: indexPath) as! HomeViewCell//取出itemcell.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的流水布局

美丽说demo(纯swift代码)-编程知识网

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 = marginminimumLineSpacing = margin//设置内边距collectionView?.contentInset = UIEdgeInsets(top: margin + 64, left: margin, bottom: margin, right: margin)}
}
—-> 2.1 解析 : 注意需要设置上下左右的内边距还有需要进行下面图片的配置才能出现图片

美丽说demo(纯swift代码)-编程知识网

—-> 2.2 需要将系统的流水布局设置为自定义的才能达到效果

七 自定义cell

1 这个相比大家都知道,对于collectionView是必须自定义cell的,不要问我为什么,这是规定.

2 拖入个UIImageView控件到cell中,然后自动布局好约束,通过拖线的方式拿到UIImageView对象

美丽说demo(纯swift代码)-编程知识网

3 自定义cell中的代码

import UIKit
import SDWebImageclass HomeViewCell: UICollectionViewCell {@IBOutlet weak var imageView: UIImageView!//didSet方法var shops : ShopItem? {didSet {//1. 校验guard let urlString = shops?.z_pic_url else {return}//2.创建urllet url = NSURL(string: urlString)//3.设置图片imageView.sd_setImageWithURL(url, placeholderImage: UIImage(named: "empty_picture"))}}
}

八 调用方法,发送网络请求

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 = shopsshowPhotoBrowserVC.view.backgroundColor = UIColor.redColor()//设置model出控制器的模式showPhotoBrowserVC.modalPresentationStyle = .Custom
//        showPhotoBrowserVC.modalTransitionStyle = .PartialCurl//设置动画代理showPhotoBrowserVC.transitioningDelegate = photoBrowserAnimator//model出控制器presentViewController(showPhotoBrowserVC, animated: true, completion: nil)}
}

十 展示点击图片的控制器

1 创建文件

美丽说demo(纯swift代码)-编程知识网

2 由总体的功能图我们可以看出,view是支持左右滑动的,并不支持上下滑动.所以我们可以通过创建一个普通的UIViewController,在上面加上一个UICollectionView就可以了.

3 创建一个类,用来创建按钮的时候,直接使用里面的方法

美丽说demo(纯swift代码)-编程知识网

4 便利构造函数(我前面有介绍)

//创建类
extension UIButton {//便利构造函数//函数前面必须加上convenience关键字convenience init(title : String, bgColor : UIColor, fontSize : CGFloat) {self.init()backgroundColor = bgColorsetTitle(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 设置collectionViewcollectionView.frame = view.bounds//2.2 设置关闭按钮let colseBtnX : CGFloat = 20.0let colseBtnW : CGFloat = 90.0let colseBtnH : CGFloat = 32.0let colseBtnY : CGFloat = view.bounds.height - colseBtnH - 20.0colseBtn.frame = CGRectMake(colseBtnX, colseBtnY, colseBtnW, colseBtnH)//2.3 设置保存按钮let saveBtnX = view.bounds.width - colseBtnW - 20saveBtn.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}
}

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! PhotoBrowerViewCellcell.shopItem = shopItem?[indexPath.item]return cell}func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {//调用closeBtnClick方法closeBtnClick()}
}

8 监听关闭和保存按钮的点击

extension PhotoBrowserController {@objc private func closeBtnClick () {
//        print("closeBtnClick")//dismiss控制器dismissViewControllerAnimated(true, completion: nil)}@objc private func saveBtnClick() {
//        print("saveBtnClick")//取出在屏幕中显示的celllet 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 = 0minimumLineSpacing = 0scrollDirection = .Horizontal//设置分页collectionView?.pagingEnabled = truecollectionView?.showsHorizontalScrollIndicator = false}
}

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. 计算展示的图片的frameimageView.frame = calculate(smallImage)//5. 获取大图片的url是否为空guard let bigUrlString = shopItem?.z_pic_url else {return}//6 .创建大图的urllet bigUrl = NSURL(string: bigUrlString)//7. 加载大图imageView.sd_setImageWithURL(bigUrl, placeholderImage: smallImage, options: .RetryFailed) {(image, error, type, url) -> Void inself.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")}
}
—-> 10.3 解析 : 下面这对代码是成对出现的
// MARK: - 构造函数override  init(frame: CGRect) {super.init(frame: frame)setUpUI()}required init?(coder aDecoder: NSCoder) {fatalError("init(coder:) has not been implemented")}

11 设置加载后图片的摆放位置

//MARK: - 设置图片的frame
extension PhotoBrowerViewCell {private func calculate (image : UIImage) ->CGRect {let imageViewX : CGFloat = 0.0let imageViewW : CGFloat = UIScreen.mainScreen().bounds.widthlet imageViewH : CGFloat = imageViewW / image.size.width * image.size.heightlet imageViewY : CGFloat = (UIScreen.mainScreen().bounds.height - imageViewH) * 0.5return 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 创建转场动画类

美丽说demo(纯swift代码)-编程知识网

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 = truereturn self}//消失动画交给谁管理func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {isPresented = falsereturn self}
}

5 自定义(通过改变透明度实现)

extension PhotoBrowserAnimator : UIViewControllerAnimatedTransitioning {//返回动画执行的时间func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {return 2.0}//获取转场的上下文:可以通过上下文获取到执行动画的viewfunc animateTransition(transitionContext: UIViewControllerContextTransitioning) {isPresented ? animateForPresentView(transitionContext) : animateForDismissed(transitionContext)}
—-> 5.1 弹出动画
//弹出func animateForPresentView(transitionContext: UIViewControllerContextTransitioning) {//1 取出弹出的viewlet presentView = transitionContext.viewForKey(UITransitionContextToViewKey)!//2 将弹出的view加入到contentView中transitionContext.containerView()?.addSubview(presentView)//3 执行动画presentView.alpha = 0.0UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void inpresentView.alpha = 1.0}) { (_) -> Void in//完成动画transitionContext.completeTransition(true)}}
—-> 5.2 消失动画
//消失func animateForDismissed (transitionContext: UIViewControllerContextTransitioning) {//1 取出消失的viewlet dismissView = transitionContext.viewForKey(UITransitionContextFromViewKey)!//2 执行动画UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void indismissView.alpha = 0.0}) { (_) -> Void in//移除viewdismissView.removeFromSuperview()//完成动画transitionContext.completeTransition(true)}}

十三 细节

1 每个cell之间应该存在距离,但是直接通过设置: minimumInteritemSpacing和minimumLineSpacing两个属性是无法达到效果的.那么我这里有一种方法,直接collectionView的宽度增加一点,但是图片的宽度还是屏幕那么大,那么用户是看不到超出屏幕的view的,用下面代码就可以搞定.

//设置cell之间的间隔(这种方法是通过将collectionView的宽度增加来实现的)override func loadView() {super.loadView()view.frame.size.width += 15}

十四 总结

1 这部分美丽说demo还不是很完善,还有一个动画翻转的方式还没有实现,那中方式是一种比较难的方式,但是达到的效果很好,后续我还是会补上的.最后,麻烦大家多多关注我的博客,谢谢!!!!