项目需求:做一个图片浏览器,点击图片查看大图,大图模式下,左右滚动能查看不同的图片.
项目的主要核心技术:图片的弹出和消失动画
 
 
项目源代码: Photo-Browser
 
一.对代码进行重构
1.对代码进行抽取划分
     1.1 为什么要对代码进行抽取?
          swift中,代码全部写在一起,阅读性极差
     
2.如何对代码进行抽取?
     2.1在oc中,可以把功能模块抽取一个个方法
 
     2.2swift中,专门提供 extension ,可以对原有的类进行扩展
 
3.怎么使用extension 抽取代码?
    3.1 把一些方法写在extension(扩展)里面,这样能减少viewDidLoad里面的代码
 
     3.2 extension可以写多个,这样就可以把不同的功能模块 ,写在不同的扩展里面
 

二.项目基本设置
 
1.修改bundleID
2.部署版本
3.设置项目图片,启动图片
4.对文件夹目录进行划分
 
三.首页布局
 
1.让首页为UICollectionViewController
2.设置数据源
 
3.自定义布局
     3.1 创建一个源文件,继承自UICollectionViewFlowLayout
     3.2 重写 prepareLayout
     3.3 设置布局的相关属性
 
4.如何设置StoryBoard中的UICollectionViewController的布局
     4.1 在StoryBoard中选中collectionView 
     4.2 在属性里找到 layout 设置为自定义  custom
     4.3 在下面的class里面 把自定义布局的类名写进去即可
 
 
四.网络工具类的封装
 
1.集成CocoaPods, 并导入AFNetworking框架
     1.1 打开终端,进入项目路径下  cd  路径
     1.2 创建PodFile文件  pod init
     1.3 配置PodFile文件 ,写入要导入的框架
  
     
     1.4 导入框架 pod install —no-repo-update / 或 pod intall
          1.41 pod install 会更新本地库(本地已有的框架也会更新) 速度相对较慢
          1.42 pod install —no-repo-update 不会更新本地库,速度相对来说快点
 
2.封装工具类
     2.1 将工具类设计成单例对象
          防止别人修改
          防止多线程访问,创建多个对象
 
     2.2 swift中单例的设置方式
          static let shareInstance : NetworkTools = NetworkTools()
 
     2.3 可以让工具类,直接继承自用到框架的一个类
          好处:自己就是这个类的子类,拥有这个类的所有方法和属性,用的时候直接自己就能调用
 

3.封装网络请求方法
 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 - {
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.23 UIImageView的高度,就等于  图片高度 * 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 ??
 
十四.设置大图之间的间距
 
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()[] 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
} // 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)  这个数值随便写
 
 
 项目源代码: Photo-Browser
 
 

swift项目初体验--教你打造一款个性化图片浏览器(篇幅过大,慎入)的更多相关文章

  1. 微信小程序开发初体验--教你开发小程序

    微信小程序 微信小程序面世以来受到的关注颇多,直到最近我才动手尝试进行了小程序的开发,总体上感觉还是不错的,有一点不适应的就是要摆脱Web APP开发对DOM的操作.在这里我就把我是如何利用API开发 ...

  2. 阿里云部署Java web项目初体验(转)/linux 上配置jdk和安装tomcat

    摘要:本文主要讲了如何在阿里云上安装JDK.Tomcat以及其配置过程.最后以一个实例来演示在阿里云上部署Java web项目. 一.准备工作 购买了阿里云的云解析,和云服务器ecs. 2.下载put ...

  3. 阿里云部署Java web项目初体验(转)

    林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:本文主要讲了如何在阿里云上安装JDK.Tomcat以及其配置过程.最后以一个实例来演示在 ...

  4. vscode 创建.net core项目初体验

    微软的virtual studio编辑器那是宇宙第一大编辑器,可惜就是太笨重,遇到性能差一些的电脑设备,简直无法快速的编辑项目. 而vs code编辑器轻便易用,想要编辑哪种项目,只需扩展插件就OK, ...

  5. Python+Django(Python Web项目初体验)

    参考:https://blog.csdn.net/qq_34081993/article/details/79229784 Django是一个开放源代码的Web应用框架,由Python写成. 安装Dj ...

  6. 十七、IntelliJ IDEA 中的 Maven 项目初体验及搭建 Spring MVC 框架

    我们已经将 IntelliJ IDEA 中的 Maven 项目的框架搭建完成.接着上文,在本文中,我们更近一步,利用 Tomcat 运行我们的 Web 项目. 如上图所示,我们进一步扩展了项目的结构, ...

  7. 阿里云部署Java web项目初体验

    林炳文Evankaka原创作品. 转载请注明出处http://blog.csdn.net/evankaka 摘要:本文主要讲了怎样在阿里云上安装JDK.Tomcat以及其配置过程. 最后以一个实例来演 ...

  8. Fragment为载体可自己主动布局的CardView(GitHub上写开源项目初体验)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 开篇废话: 前些天一直在看Android5.0 的Material Desgin,里面新增 ...

  9. Axure初体验:简单交互、通过按钮切换图片

    前言: 之前是一直用processon的UI原型设计,后来感觉只能完成静态页面的processon满足不了原型设计的需求,断网时候也不方便修改.展示.最终还是决定学习动态页面的制作,所选工具为原型设计 ...

随机推荐

  1. 【COGS】894. 追查坏牛奶

    http://cojs.tk/cogs/problem/problem.php?pid=894 题意:n个点m条边的加权网络,求最少边数的按编号字典序最小的最小割.(n<=32, m<=1 ...

  2. SRM 595 DIV2 1000

    数位DP的感觉,但是跟模版不是一个套路的,看的题解,代码好理解,但是确实难想. #include <cstdio> #include <cstring> #include &l ...

  3. 数组的Clone方法

    public void Test() { ,,}; var arr2 = arr1; var arr3 = (int[])arr1.Clone(); //浅拷贝 arr1[] = ; //arr2[0 ...

  4. Rational Rose 2007 破解版安装过程

    Rational Rose 2007 破解版安装过程 首先通过网站将软件下载,然后依照以下步骤进行: 选择第二项,下一步 一直点击next,出现如下,可以修改安装的目的文件夹 设置完路径之后出现如下, ...

  5. windows一些快捷键

    1.Win + __ 1)Win + L 锁屏 2)Win + E 资源管理器(就是打开硬盘) 3)Win + D 回到桌面 4)Win + Tab 3D方式切换程序窗口 5)Win + R 运行命令 ...

  6. 队列 Soldier and Cards

    Soldier and Cards 题目: Description Two bored soldiers are playing card war. Their card deck consists ...

  7. Qt Write and Read XML File 读写XML文件

    在Qt中,我们有时候需要把一些参数写入xml文件,方便以后可以读入,类似一种存档读档的操作,例如,我们想生成如下的xml文件: <?xml version="1.0" enc ...

  8. oracle 连接查询,和(+)符号的用法

    --连接查询 左链接.右链接,全链接 --内链接select e.account 用户名, e.empname 名称, c.comname 公司名称  from employee e inner jo ...

  9. 非静态的字段、方法或属性“System.Web.UI.Page.ClientScript...”要求对象引用 (封装注册脚本)

    在写项目时想对asp.net的注册前台脚本事件进行封装,就添加了一个BasePage.cs页面,但一直报错‘非静态的字段.方法或属性“System.Web.UI.Page.ClientScript.. ...

  10. Java关键字final、static使用总结

    Java关键字final.static使用总结   一.final        根据程序上下文环境,Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类.非抽象类成员方 ...