[Swift通天遁地]九、拔剑吧-(11)创建强大的Pinterest风格的瀑布流界面
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/10357522.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
本文将演示如何创建强大的Pinterest风格的瀑布流界面。
Github项目:【demonnico/PinterestSwift】,下载并解压文件。
【PinterestSwift】文件夹->【CHTCollectionViewWatrfallLayout.swift】文件
->按下【Command】,继续选择【Extension.swift】->继续选择【Macro.swift】
->继续选择【NTHorizontalPagViewCell.swift】
->继续选择【NTTransition.swift】
->继续选择【NTTransitionProtocol.swift】
->继续选择【NTWaterfallViewCell.swift】
将上面选择的文件,拖入到自己的项目中。
在弹出的文件导入确认窗口中,点击【Finish】完成按钮,确认文件的导入。
在项目文件夹上点击鼠标右键,弹出右键菜单。
【New File】->【Cocoa Touch】->【Next】->
【Class】:HorizontalPageViewController
【Subclass of】:UICollectionViewController
【Language】:Swift
->【Next】->【Create】
点击打开【HorizontalPageViewController.swift】,
现在开始编写代码,创建一个自定义的集合视图。
1 import Foundation 2 import UIKit 3 4 //初始化一个字符串常量,作为集合单元格的复用标识。 5 let horizontalPageViewCellIdentify = "horizontalPageViewCellIdentify" 6 7 //给当前的类添加两个协议,使用第一个协议中的方法,返回页面切换所需的集合视图, 8 //使用第二个协议的方法,设置集合视图的单元格的偏移距离。 9 class HorizontalPageViewController : UICollectionViewController, NTTransitionProtocol ,NTHorizontalPageViewControllerProtocol 10 { 11 //添加一个字符串数组属性,作为集合视图每个单元格显示的图片内容。 12 var imageNameList : Array <NSString> = [] 13 //添加一个属性,作为集合视图单元格的偏移距离。 14 var pullOffset = CGPoint.zero 15 16 //添加一个初始化方法,用来设置集合视图的布局。 17 init(collectionViewLayout layout: UICollectionViewLayout!, currentIndexPath indexPath: IndexPath) 18 { 19 super.init(collectionViewLayout:layout) 20 //获得集合视图控制器的集合视图。 21 let collectionView :UICollectionView = self.collectionView!; 22 //设置集合视图在页面跳转时自动停止滚动 23 collectionView.isPagingEnabled = true 24 //使用第三方类库提供的集合单元格类,注册集合视图的单元格,并设置单元格的复用标识。 25 collectionView.register(NTHorizontalPageViewCell.self, forCellWithReuseIdentifier: horizontalPageViewCellIdentify) 26 //通过扩展方法,把一个对象与另外一个对象进行关联。 27 //这需要使用到运行时函数,包含四个参数: 28 //源对象、关键字、关联的对象、一个关联策略。 29 collectionView.setToIndexPath(indexPath) 30 31 //调用集合视图对象的序列刷新方法,该方法可以执行多个插入、删除、重新加载、移动等操作。 32 collectionView.performBatchUpdates({collectionView.reloadData()}, completion: { finished in 33 if finished 34 { 35 //调用集合视图对象的滑动到指定项目方法,在垂直方向上,滑动到指定索引的单元格。 36 collectionView.scrollToItem(at: indexPath,at:.centeredHorizontally, animated: false) 37 }}); 38 } 39 40 override func viewDidLoad() 41 { 42 super.viewDidLoad() 43 } 44 45 //添加一个代理方法,用来初始化或复用集合视图中的单元格。 46 override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 47 { 48 //根据复用标识,从集合视图中获取可以复用的单元格。 49 let collectionCell: NTHorizontalPageViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: horizontalPageViewCellIdentify, for: indexPath) as! NTHorizontalPageViewCell 50 51 //设置集合视图的扩展设置,从而往集合视图中添加一个图像视图。 52 collectionCell.imageName = self.imageNameList[(indexPath as NSIndexPath).row] as String 53 //初始化集合视图的扩展属性。 54 collectionCell.tappedAction = {} 55 //给集合视图添加一个下拉动作。 56 collectionCell.pullAction = { offset in 57 //当接收到下拉事件时, 58 self.pullOffset = offset 59 //在导航控制器的堆栈中,返回上一个页面。 60 self.navigationController!.popViewController(animated: true) 61 } 62 //在绘制周期开始前,首先对集合视图进行布局, 63 collectionCell.setNeedsLayout() 64 65 //最后返回设置好的集合视图单元格。 66 return collectionCell 67 } 68 69 //添加一个代理方法,用来设置集合视图的单元格的数据。 70 override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 71 { 72 return imageNameList.count 73 } 74 75 //添加一个代理方法,用来返回页面切换所需的集合视图 76 func transitionCollectionView() -> UICollectionView! 77 { 78 return collectionView 79 } 80 81 //添加一个代理方法,设置集合视图的单元格的偏移距离。 82 func pageViewCellScrollViewContentOffset() -> CGPoint 83 { 84 return self.pullOffset 85 } 86 87 //添加一个必须实现的初始化方法 88 required init?(coder aDecoder: NSCoder) 89 { 90 fatalError("init(coder:) has not been implemented") 91 } 92 }
在左侧的项目导航区,打开视图控制器的代码文件【ViewController.swift】
现在开始编写代码,将系统默认的视图控制器,修改为另一个集合视图控制器,
作为瀑布流的载体。
1 import UIKit 2 3 let waterfallViewCellIdentify = "waterfallViewCellIdentify" 4 5 //创建一个遵循导航控制器代理协议的类 6 class NavigationControllerDelegate: NSObject, UINavigationControllerDelegate 7 { 8 //添加一个方法,用来处理导航控制器页面之间的跳转事件, 9 //并返回页面跳转的动画样式。 10 func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?{ 11 12 //初始化三个对象,作为导航控制器跳转前的页面。 13 let fromVCConfromA = (fromVC as? NTTransitionProtocol) 14 let fromVCConfromB = (fromVC as? NTWaterFallViewControllerProtocol) 15 let fromVCConfromC = (fromVC as? NTHorizontalPageViewControllerProtocol) 16 17 //初始化三个对象,作为导航控制器跳转后的页面。 18 let toVCConfromA = (toVC as? NTTransitionProtocol) 19 let toVCConfromB = (toVC as? NTWaterFallViewControllerProtocol) 20 let toVCConfromC = (toVC as? NTHorizontalPageViewControllerProtocol) 21 22 //判断当从瀑布流页面,跳转到详情页面,或者从详情页面,返回瀑布流页面时的跳转样式。 23 if((fromVCConfromA != nil)&&(toVCConfromA != nil)&&( 24 (fromVCConfromB != nil && toVCConfromC != nil)||(fromVCConfromC != nil && toVCConfromB != nil))){ 25 //初始化一个动画跳转对象 26 let transition = NTTransition() 27 //根据导航控制器的页面跳转的类型是否为出栈操作, 28 //来设置跳转对象的布尔属性。 29 transition.presenting = operation == .pop 30 //最后返回设置好的切换对象。 31 return transition 32 } 33 else 34 { 35 return nil 36 } 37 } 38 } 39 40 //修改当前视图控制器的父类,将父类修改为集合视图控制器, 41 //并遵循瀑布流布局协议、动画切换协议、以及瀑布流视图控制器协议。 42 class ViewController:UICollectionViewController, CHTCollectionViewDelegateWaterfallLayout, NTTransitionProtocol, NTWaterFallViewControllerProtocol{ 43 44 //初始化一个字符串数组,作为集合视图所有图像的名称。 45 var imageNameList : Array <NSString> = [] 46 //初始化一个导航控制器代理对象。 47 let delegateHolder = NavigationControllerDelegate() 48 override func viewDidLoad() 49 { 50 super.viewDidLoad() 51 // Do any additional setup after loading the view, typically from a nib. 52 //设置当前导航控制器的代理对象。 53 self.navigationController!.delegate = delegateHolder 54 55 var index = 1 56 //添加一个循环语句,用来往数组中添加图片的名称。 57 while(index<14) 58 { 59 //根据循环的索引,初始化一个由图片名称组成的字符串数组。 60 let imageName = NSString(format: "Pic%d.png", index) 61 //将图片名称添加到数组中。 62 imageNameList.append(imageName) 63 index += 1 64 } 65 66 //获得当前集合视图控制器中的集合视图。 67 let collection :UICollectionView = collectionView! 68 //设置集合视图的显示区域与屏幕相同,在此使用了宏定义常量。 69 collection.frame = screenBounds 70 //设置集合视图的布局样式 71 collection.setCollectionViewLayout(CHTCollectionViewWaterfallLayout(), animated: false) 72 //设置集合视图的背景颜色为黑色 73 collection.backgroundColor = UIColor.black 74 //给集合视图注册复用标识符 75 collection.register(NTWaterfallViewCell.self, forCellWithReuseIdentifier: waterfallViewCellIdentify) 76 //重新加载集合视图中的数据 77 collection.reloadData() 78 } 79 80 //添加一个方法,用来设置单元格的尺寸 81 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize 82 { 83 //加载数组中的指定名称的图片 84 let image:UIImage! = UIImage(named: self.imageNameList[(indexPath as NSIndexPath).row] as String) 85 //单元格的宽度时固定的,在此根据单元格的高度和图片的宽度,获得等比例的图片高度。 86 let imageHeight = image.size.height*gridWidth/image.size.width 87 88 //返回计算好的图片尺寸 89 return CGSize(width: gridWidth, height: imageHeight) 90 } 91 92 //添加一个代理方法,用来初始化或复用集合视图中的单元格。 93 override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 94 { 95 //根据复用标识,从集合视图中获取可以复用的单元格。 96 let collectionCell: NTWaterfallViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: waterfallViewCellIdentify, for: indexPath) as! NTWaterfallViewCell 97 //设置集合视图的扩展方法,从而往集合视图中添加一个图像视图。 98 collectionCell.imageName = self.imageNameList[(indexPath as NSIndexPath).row] as String 99 //对单元格在绘制之前进行重新布局。并返回设置好的单元格。 100 collectionCell.setNeedsLayout() 101 return collectionCell 102 } 103 104 //添加一个代理方法,用来设置单元格的数量 105 override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 106 { 107 return imageNameList.count 108 } 109 110 //添加一个代理方法,用来处理单元格的触摸事件。 111 override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) 112 { 113 //初始化一个视图控制器,作为即将显示的细节页面。 114 let pageViewController = 115 HorizontalPageViewController(collectionViewLayout: pageViewControllerLayout(), currentIndexPath:indexPath) 116 //设置控制器的图片名称列表属性 117 pageViewController.imageNameList = imageNameList 118 //在集合视图中,跳转到指定位置的单元格 119 collectionView.setToIndexPath(indexPath) 120 //在导航控制器的堆栈中,压入新的集合控制器。 121 navigationController!.pushViewController(pageViewController, animated: true) 122 } 123 124 //添加一个方法,用来设置集合视图的布局方式 125 func pageViewControllerLayout () -> UICollectionViewFlowLayout 126 { 127 //初始化一个集合视图流布局对象。 128 let flowLayout = UICollectionViewFlowLayout() 129 //根据导航栏的显示状态,创建集合视图的尺寸。 130 let itemSize = self.navigationController!.isNavigationBarHidden ? 131 CGSize(width: screenWidth, height: screenHeight+20) : CGSize(width: screenWidth, height: screenHeight-navigationHeaderAndStatusbarHeight) 132 //设置集合视图的单元格的尺寸。 133 flowLayout.itemSize = itemSize 134 //设置单元格之间的最小行距 135 flowLayout.minimumLineSpacing = 0 136 //设置同一行的单元格之间的最小间距。 137 flowLayout.minimumInteritemSpacing = 0 138 //设置布局对象的滚动方向为水平方向。 139 flowLayout.scrollDirection = .horizontal 140 141 //返回设置好的布局对象 142 return flowLayout 143 } 144 145 //添加一个方法,用来返回进行动态切换的集合视图 146 func transitionCollectionView() -> UICollectionView! 147 { 148 return collectionView 149 } 150 151 func viewWillAppearWithPageIndex(_ pageIndex : NSInteger) 152 { 153 154 } 155 156 override func didReceiveMemoryWarning() { 157 super.didReceiveMemoryWarning() 158 // Dispose of any resources that can be recreated. 159 } 160 }
在项目导航区,打开故事板文件。
在故事板中添加一个集合视图控制器,首先选择并删除默认的视图控制器。
选择默认的视图控制器,【Command】+【Delete】删除选择的视图控制器。
点击控件库图标,打开控件库的列表窗口。双击集合视图控制器,往故事板中插入一个控制器。
依次点击:【Editor】编辑器->【Embed In】植入->【Navigation Controller】导航控制器
将集合视图控制器植入导航控制器。植入导航控制器。
打开检查器设置面板,点击属性检查器图标,进入属性设置面板。
勾选【Is Initial View Controller】是否初始视图控制器。
将导航控制器修改为项目的初始控制器。
选择集合视图控制器,点击身份检查器图标,打开身份设置面板。
在类名输入框内,输入所绑定的自定义集合视图类。
【Class】:ViewController
模拟器启动后,由下往上拖动,可以浏览瀑布流底部的内容。
在详情页面的顶部往下方拖动,
通过下拉动作,返回瀑布流页面。