Swift:用UICollectionView整一个瀑布流
我們要干點(diǎn)啥
用新浪微博的Open API做后端來實(shí)現(xiàn)我們要提到的功能。把新浪微博的內(nèi)容,圖片和文字展示在collection view中。本文只簡單的展示內(nèi)容。下篇會用pinterest一樣的效果來展示這些內(nèi)容。
我們準(zhǔn)備優(yōu)先展示圖片。你的好友花了那么多時(shí)間拍照或者從相冊里選擇圖片發(fā)上來多不容易。如果微博返回的數(shù)據(jù)中有中等大小的縮略圖,那么久展示這個(gè)縮略圖。否則的話顯示文本。文本都沒有的話。。。這個(gè)就不是微博了。但是我們還是會準(zhǔn)備一個(gè)顏色顯示出來。
啥是UICollectionView
UICollectionView有一個(gè)靈活的布局,可以用各種不同的布局展示數(shù)據(jù)。?
UICollectionView的使用和UITableView類似,也是需要分別去實(shí)現(xiàn)一組datasource的代理和UICollectionView本身的代理來把數(shù)據(jù)展示在界面中。
其他的還有:?
1. UICollectionViewCell:這些Cell組成了整個(gè)UICollectionView,并作為子View添加到UICollectionView中。可以在Interface builder中創(chuàng)建,也可以代碼創(chuàng)建。?
2. Header/Footer:跟UITableView差不多的概念。顯示一些title什么的信息。
除了以上說到的內(nèi)容之外,collection view還有一個(gè)專門處理布局的UICollectionViewLayout。你可以繼承UICollectionViewLayout來創(chuàng)建一個(gè)自己的collection view的布局。蘋果給了一個(gè)基礎(chǔ)的布局UICollectionViewFlowLayout,可以實(shí)現(xiàn)一個(gè)基本的流式布局。這些會在稍后的教程中介紹。
開始我們的項(xiàng)目:?
首先創(chuàng)建一個(gè)single view的應(yīng)用。?
然后給你的項(xiàng)目起一個(gè)名字,我們這里就叫做CollectionViewDemo。Storyboard中默認(rèn)生成的Controller已經(jīng)木有什么用處了。直接干掉,拖一個(gè)UICollectionViewController進(jìn)去并設(shè)置為默認(rèn)的Controller。并刪除默認(rèn)生成的ViewController.swift文件,并創(chuàng)建一個(gè)叫做HomeCollectionViewController.swift的文件。之后在interface builder中把collection view的類設(shè)置為HomeCollectionViewController。
然后:?
接下來再次回到collection view controller。這個(gè)
進(jìn)一步了解UICollectionView
如前文所述,UICollectionView和UITableView類似,都有datasource和delegate。這樣就可以設(shè)置datasource和設(shè)置一些用戶的交互,比如選中某一個(gè)cell的時(shí)候怎么處理。
UICollectionViewFlowLayout有一個(gè)代理:UICollectionViewDelegateFlowLayout。通過這個(gè)代理可以設(shè)定布局的一些行為比如:cell的間隔,collection view的滾動方向等。
下面就開始在我們的代碼中給UICollectionViewDataSource和UICollectionViewDelegateFlowLayout?兩個(gè)代理的方法做一個(gè)填空。UICollectionViewDelegate里的方法暫時(shí)還用不著,稍后會給這個(gè)代理做填空。
實(shí)現(xiàn)UICollectionViewDataSource
這里我們用微博開放API為例。從微博的開發(fā)API上獲取到當(dāng)前用戶的全部的微博,然后用UICollectionView展示。獲取到的微博time line最后會放在這里:
private var timeLineStatus: [StatusModel]?在data source中的代碼就很好添加了。
// MARK: UICollectionViewDataSourceoverride func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {return 1 //1}override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {return self.timeLineStatus?.count ?? 0 //2}override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)cell.backgroundColor = UIColor.orangeColor() //3return cell}效果是這樣的:?
UICollectionViewFlowLayoutDelegate
這個(gè)代理的作用和UITableView的func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat有非常類似的作用。heightForRowAtIndexPath的作用是返回UITableViewCell的高度。而UICollectionViewCell有非常多的不同的大小,所以需要更加復(fù)雜的代理方法的支持。其中包括兩個(gè)方法:
// 1 class HomeCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout // 2 private let sectionInsets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0) // MARK: UICollectionViewDelegateFlowLayout// 3 func collectionView(collectionView: UICollectionView, layout collectionViewLayout:UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {return CGSize(width: 170, height: 300) }// 4 func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {return sectionInsets }看看運(yùn)行效果:?
創(chuàng)建自定義UICollectionViewCell
下面就要處理內(nèi)容在展示的時(shí)候具體應(yīng)該怎么展示了。我們這里分兩種情況,如果用戶的微博有圖片,那么就展示圖片。如果沒有圖片就展示文字。可惜的是微博的API沒有圖片的大小返回回來。展示的時(shí)候需要大小參數(shù)來決定這個(gè)?
UICollectionViewCell到底要多大的size,由于沒有就只好弄個(gè)方塊來展示圖片了。至于圖片的拉伸方式就有你來決定了,我們這里為了簡單就使用默認(rèn)的方式拉伸圖片。
在文字上就需要根據(jù)文字的多少了決定size了。由于我們的寬度是一定的,也就是說在autolayout中UILabel的preferredMaxLayoutWidth是一定的。然后就可以很方便的根據(jù)這個(gè)寬度來計(jì)算多行的UILabel到底需要多少的高度來全部展示微博中的文字。
首先是展示圖片的Cell。?
在Cell上放一個(gè)UIImageView,保證這個(gè)image view的四個(gè)邊距都是0。
創(chuàng)建一個(gè)文件WeiboImageCell.swift,里面是類WeiboImageCell,繼承自UICollectionViewCell。?
把這個(gè)Cell的custom class設(shè)置為WeiboImageCell。?
?
然后把Cell代碼中的image view和interface builder的image view關(guān)聯(lián)為IBOutelt:
重復(fù)上面的步驟添加一個(gè)只有一個(gè)UILabel的Cell,類型為WeiboTextCell。設(shè)置這個(gè)UILabel的屬性numberOfLines為0,這樣就可以顯示多行的文字。然后設(shè)置這個(gè)label的上、左、下、右都是-8。
為什么是-8呢,因?yàn)樘O果默認(rèn)的給父view留了寬度為8的margin(邊距),如果要文字和Cell的邊距貼合的話 需要覆蓋這個(gè)系統(tǒng)預(yù)留的邊距,因此需要設(shè)置邊距為-8。最后關(guān)聯(lián)代碼和label。
class WeiboTextCell: UICollectionViewCell {@IBOutlet weak var weiboTextLabel: UILabel! }添加完這兩個(gè)Cell之后,回到HomeCollectionViewController。刪除self.collectionView!.registerClass(WeiboImageCell.self, forCellWithReuseIdentifier: reuseIdentifier)方法,以及全部的registerClass。
`registerClass`, 這個(gè)方法的調(diào)用會把我們在storyboard里做的一切都給抹掉。在調(diào)用Cell里的image view或者label的時(shí)候得到的永遠(yuǎn)是nil。到這,我們需要討論一下text cell對于label的約束問題。首先我們同樣設(shè)置label的約束,讓這個(gè)label貼著cell的邊。也就是,top、leading、trailing和bottom為-8。
但是這樣的而設(shè)置讓label在顯示出來的cell中是居中的。尤其在文字不足夠現(xiàn)實(shí)滿cell的空間的時(shí)候。所以,我們需要改一個(gè)地方。修改bottom的優(yōu)先級,設(shè)置為low,最低:UILayoutPriorityDefaultLow。這樣在labe計(jì)算高度的時(shí)候會優(yōu)先考慮的是文字填滿label后的高度,而不是像之前一樣直接把labe的高度設(shè)置為cell的高度。這個(gè)時(shí)候不論文字是否填滿cell,都是從頂開始顯示有多少控件用多少空間。
集成SDWebImage
我們那什么來拯救圖片cell惹?辣就是SDWebImage是一個(gè)著名的圖片請求和緩存的庫。我們這里用這個(gè)庫來請求微博中的圖片并緩存。
添加:?
在Podfile里添加SDWebImage的pod應(yīng)用pod ‘SDWebImage’, ‘~>3.7’。當(dāng)然了之前我們已經(jīng)添加了user_frameworks!。為什么用這個(gè)看原文:
多了就不多說了,需要了解更多的可以看這里。
pod更新完成之后。引入這個(gè)framework。
import SDWebImage然后就可以給cell的image view上圖片了。
weiboImageCell.weiboImageView.sd_setImageWithURL(NSURL(string: status.status?.bmiddlePic ?? ""))SDWebImage給image view寫了一個(gè)category。里面有很多可以調(diào)用的方法。比如可以設(shè)置一個(gè)place holder的image。也就是在image沒有下載下來之前可以給image view設(shè)置一個(gè)默認(rèn)的圖片。
http請求和數(shù)據(jù)
這里只是簡單說一下,更過的內(nèi)容請看這里。?
下面我們看看微博的Open API能給我們返回什么:
我們只需要我們follow的好友的微博的圖片或者文字。所以由這些內(nèi)容我們可以定義出對應(yīng)的model類。
import ObjectMapperclass BaseModel: Mappable {var previousCursor: Int?var nextCursor: Int?var hasVisible: Bool?var statuses: [StatusModel]?var totalNumber: Int?required init?(_ map: Map) {}func mapping(map: Map) {previousCursor <- map["previous_cursor"]nextCursor <- map["next_cursor"]hasVisible <- map["hasvisible"]statuses <- map["statuses"]totalNumber <- map["total_number"]} }和
import ObjectMapperclass StatusModel: BaseModel {var statusId: String?var thumbnailPic: String?var bmiddlePic: String?var originalPic: String?var weiboText: String?var user: WBUserModel?required init?(_ map: Map) {super.init(map)}override func mapping(map: Map) {super.mapping(map)statusId <- map["id"]thumbnailPic <- map["thumbnail_pic"]bmiddlePic <- map["bmiddle_pic"]originalPic <- map["original_pic"]weiboText <- map["text"]} }其中內(nèi)容全部都放在類StatusModel中,圖片我們用屬性bmiddlePic,文字用weiboText。其他屬性留著以后使用。
請求完成以后,這些time line的微博會存在一個(gè)屬性里做為數(shù)據(jù)源使用。
class HomeCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {private var timeLineStatus: [StatusModel]? // 1//2Alamofire.request(.GET, "https://api.weibo.com/2/statuses/friends_timeline.json", parameters: parameters, encoding: .URL, headers: nil).responseString(completionHandler: {response inlet statuses = Mapper<BaseModel>().map(response.result.value)if let timeLine = statuses where timeLine.totalNumber > 0 {self.timeLineStatus = timeLine.statuses // 3self.collectionView?.reloadData()}}) }在展示數(shù)據(jù)的時(shí)候需要區(qū)分微博的圖片是否存在,存在則優(yōu)先展示圖片,否則展示文字。
一個(gè)不怎么好的做法是在方法cell for collection view里判斷數(shù)據(jù)源是否存在,遍歷每一個(gè)數(shù)據(jù)源的item判斷這個(gè)item是否有圖片。。。
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {if let statuses = self.timeLineStatus {let status = statuses[indexPath.item]if status } }這樣顯然太過冗長了,所以我們要把這一部分代碼提升出來。
/**get status and if this status has image or not@return:status, one of the timelineInt, 1: there's image, 0: there's no image, -1: empty status*/func getWeiboStatus(indexPath: NSIndexPath) -> (status: StatusModel?, hasImage: Int) { // 1if let timeLineStatusList = self.timeLineStatus where timeLineStatusList.count > 0 {let status = timeLineStatusList[indexPath.item]if let middlePic = status.bmiddlePic where middlePic != "" {// there's middle sized image to showreturn (status, 1)} else {// start to consider textreturn (status, 0)}}return (nil, -1)}swift是可以在一個(gè)方法里返回多個(gè)值的。這個(gè)多個(gè)內(nèi)容的值用tuple來存放。調(diào)用時(shí)這樣的:
let status = self.getWeiboStatus(indexPath) let hasImage = status?.hasImage // if there's a image let imageUrl = status.status?.bmiddlePic // image path let text = status.status?.weiboText // text只要通過let hasImage = status?.hasImage就可以判斷是否有圖片。所以Swift的這一點(diǎn)還是非常方便的。那么在判斷要顯示哪一種Cell的時(shí)候就非常的方便了。修改后的代碼也非常的簡潔。這個(gè)習(xí)慣需要一直保持下去。
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {let status = self.getWeiboStatus(indexPath)var cell: UICollectionViewCell = UICollectionViewCell()guard let _ = status.status else {cell.backgroundColor = UIColor.darkTextColor()return cell}if status.hasImage == 1 {cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)let weiboImageCell = cell as! WeiboImageCellweiboImageCell.weiboImageView.backgroundColor = UIColor.blueColor()weiboImageCell.weiboImageView.sd_setImageWithURL(NSURL(string: status.status?.bmiddlePic ?? ""))} else if status.hasImage == 0 {cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseTextIdentifier, forIndexPath: indexPath)let weiboTextCell = cell as! WeiboTextCellweiboTextCell.setCellWidth(self.cellWidth)weiboTextCell.weiboTextLabel.text = status.status?.weiboText ?? ""weiboTextCell.contentView.backgroundColor = UIColor.orangeColor()weiboTextCell.weiboTextLabel.backgroundColor = UIColor.redColor()} else {cell = UICollectionViewCell()}cell.backgroundColor = UIColor.orangeColor() //3return cell}跑起來,看看運(yùn)行效果。?
好丑!!!
?
全部代碼在這里。
to be continued
總結(jié)
以上是生活随笔為你收集整理的Swift:用UICollectionView整一个瀑布流的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UIImagePickerControl
- 下一篇: 3Delight NSI: A Stre