一 总体实现功能图(由于csdn上传的视频有规定大小,这里只简短的录了点,抱歉)
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 {
static let shareIntance : NetworkTools = {
let tools = NetworkTools()
tools.responseSerializer.acceptableContentTypes?.insert("text/html")
return tools
}()
}
3 定义枚举 : 定义请求方式的枚举,用来作为参数传递,作为判断选择哪种方式进行请求
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/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 创建模型 :
2 根据我们自己的需求,将请求出来的数据,进行在线格式化,然后抽取我们自己需要的模型数据属性.我们做的demo只需要小图和大图就可以.
class ShopItem: NSObject {
var q_pic_url = ""
var z_pic_url = ""
init(dict : [String : AnyObject]) {
super.init()
setValuesForKeysWithDictionary(dict)
}
override func setValue(value: AnyObject?, forUndefinedKey key: String) {
}
}
—-> 2.1 我们直接用KVC的方式直接给模型的属性赋值,不采取MJ的框架了.但是会报错,因为属性并不是一 一对应,所以需要我们将系统报错的方法重写.只要重写就不会报错了.
五 数据源方法
1 由于我们子控制器就是采用UICollectionViewController,所以就不需要设置collectionView了.
2 这部分数据源代码可能也会很多,所以这里也采用给类扩展一个方法,在这个方法中写数据源方法
extension HomeViewController {
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.shops.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("HomeCell", forIndexPath: indexPath) as! HomeViewCell
cell.shops = shops[indexPath.item]
if indexPath.item == self.shops.count - 1 {
loadData(self.shops.count)
}
return 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)
}
}
—-> 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!
var shops : ShopItem? {
didSet {
guard let urlString = shops?.z_pic_url else {
return
}
let url = NSURL(string: urlString)
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 网络数据请求
extension HomeViewController {
func loadData(offSet : Int) {
NetworkTools.shareIntance.loadHomeData(offSet) { (result, error) -> () in
if error != nil {
print(error)
return
}
guard let resultArray = result else {
print("获取的结果不正确")
return
}
for resultDict in resultArray {
let shop : ShopItem = ShopItem(dict : resultDict)
self.shops.append(shop)
}
self.collectionView?.reloadData()
}
}
}
九 cell的点击
1 实现collectionView数据源中的一个方法
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
showPhotoBrowser(indexPath)
}
2 弹出控制器
extension HomeViewController {
private func showPhotoBrowser( indexPath : NSIndexPath) {
let showPhotoBrowserVC = PhotoBrowserController()
showPhotoBrowserVC.indexPath = indexPath
showPhotoBrowserVC.shopItem = shops
showPhotoBrowserVC.view.backgroundColor = UIColor.redColor()
showPhotoBrowserVC.modalPresentationStyle = .Custom
showPhotoBrowserVC.transitioningDelegate = photoBrowserAnimator
presentViewController(showPhotoBrowserVC, animated: true, completion: nil)
}
}
十 展示点击图片的控制器
1 创建文件
2 由总体的功能图我们可以看出,view是支持左右滑动的,并不支持上下滑动.所以我们可以通过创建一个普通的UIViewController,在上面加上一个UICollectionView就可以了.
3 创建一个类,用来创建按钮的时候,直接使用里面的方法
4 便利构造函数(我前面有介绍)
extension UIButton {
convenience init(title : String, bgColor : UIColor, fontSize : CGFloat) {
self.init()
backgroundColor = bgColor
setTitle(title, forState: .Normal)
titleLabel?.font = UIFont.systemFontOfSize(fontSize)
}
}
5 UICollectionView中的相关设置
class PhotoBrowserController: UIViewController {
let PhoptoBrowserCell = "PhoptoBrowserCell"
var shopItem : [ShopItem]?
var indexPath : NSIndexPath?
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()
setUpUI()
collectionView.scrollToItemAtIndexPath(indexPath!, atScrollPosition: .Left, animated: false)
}
6 添加并且布局子控件的位置
extension PhotoBrowserController {
func setUpUI() {
view.addSubview(collectionView)
view.addSubview(colseBtn)
view.addSubview(saveBtn)
collectionView.frame = view.bounds
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)
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
}
}
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()
}
}
8 监听关闭和保存按钮的点击
extension PhotoBrowserController {
@objc private func closeBtnClick () {
dismissViewControllerAnimated(true, completion: nil)
}
@objc private func saveBtnClick() {
let cell = collectionView.visibleCells().first as! PhotoBrowerViewCell
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
}
}
10 自定义cell
—-> 10.1 需要考虑的因素 : 1 >返回的url是否存在? 2> 能不能直接冲缓存池中获取?
—-> 10.2 代码
class PhotoBrowerViewCell: UICollectionViewCell {
lazy var imageView : UIImageView = UIImageView()
var shopItem : ShopItem? {
didSet {
guard let urlString = shopItem?.q_pic_url else {
return
}
var smallImage = SDWebImageManager.sharedManager().imageCache.imageFromMemoryCacheForKey(urlString)
if smallImage == nil {
smallImage = UIImage(named: "empty_picture")
}
imageView.frame = calculate(smallImage)
guard let bigUrlString = shopItem?.z_pic_url else {
return
}
let bigUrl = NSURL(string: bigUrlString)
imageView.sd_setImageWithURL(bigUrl, placeholderImage: smallImage, options: .RetryFailed) {(image, error, type, url) -> Void in
self.imageView.frame = self.calculate(image)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setUpUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
—-> 10.3 解析 : 下面这对代码是成对出现的
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.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
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 实现代理方法
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
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
isPresented ? animateForPresentView(transitionContext) : animateForDismissed(transitionContext)
}
—-> 5.1 弹出动画
func animateForPresentView(transitionContext: UIViewControllerContextTransitioning) {
let presentView = transitionContext.viewForKey(UITransitionContextToViewKey)!
transitionContext.containerView()?.addSubview(presentView)
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) {
let dismissView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void in
dismissView.alpha = 0.0
}) { (_) -> Void in
dismissView.removeFromSuperview()
transitionContext.completeTransition(true)
}
}
十三 细节
1 每个cell之间应该存在距离,但是直接通过设置: minimumInteritemSpacing和minimumLineSpacing两个属性是无法达到效果的.那么我这里有一种方法,直接collectionView的宽度增加一点,但是图片的宽度还是屏幕那么大,那么用户是看不到超出屏幕的view的,用下面代码就可以搞定.
//设置cell之间的间隔(这种方法是通过将collectionView的宽度增加来实现的)
override func loadView() {
super.loadView()
view.frame.size.width += 15
}
十四 总结
1 这部分美丽说demo还不是很完善,还有一个动画翻转的方式还没有实现,那中方式是一种比较难的方式,但是达到的效果很好,后续我还是会补上的.最后,麻烦大家多多关注我的博客,谢谢!!!!