在上一篇中,你學(xué)到了如何來用UICollectionView完成一個網(wǎng)格相冊.
在本篇中,你將繼續(xù)來學(xué)習(xí)collection views的更多用法.繼續(xù)使用上一篇的項(xiàng)目或者可以在這里下載(你需要上一篇提到的新的API key).
- 添加標(biāo)頭
這個應(yīng)用每個搜索結(jié)果都有一個section.可以在搜索結(jié)果之前添加一個標(biāo)頭,以便更好地顯示照片信息.
使用UICollectionReusableView來創(chuàng)建標(biāo)頭.這個類是collection view cell的一種(實(shí)際上,cells繼承于此類),但用法卻同headers或footers相似.
這個view可以在storyboard里創(chuàng)建并可以連接到你的類中.新建一個UICollectionReusableView的子類,命名為FlickrPhotoHeaderView.
在collection view的Attributes Inspector里面勾選Section Header:
你會發(fā)現(xiàn)"Collection Reusable View"將自動添加到Collection View的下面.選擇Collection Reusable View后就可以添加子視圖了.
你可以通過拖動Collection Reusable View的底部來使其變?yōu)?0pixels高,便于有更多的空間.(你也可以在屬性面板里改變view的尺寸)
在header view的中心添加一個label控件.將字體調(diào)為System 32.0并在alignment menu里添加水平和豎直相關(guān)的約束,更新frame:
選擇頭視圖,將其Class設(shè)為FlickrPhotoHeaderView.
設(shè)置背景顏色為90%的白色,視圖的Identifier設(shè)為FlickrPhotoHeaderView.這個identifier將會在下面用到.
點(diǎn)擊Assistant editor,確保FlickrPhotoHeaderView.swift打開,連線拖拽label到類中.命名為label:
class FlickrPhotoHeaderView: UICollectionReusableView {
@IBOutlet weak var label: UILabel!
}
如果現(xiàn)在就運(yùn)行程序,你將看不到header(或者將是個僅有個"Label"文字的空白頁).你需要實(shí)現(xiàn)另外的一個datasource 方法.打開FlickrPhotosViewController.swift添加UICollectionViewDataSource擴(kuò)展:
override func collectionView(collectionView: UICollectionView,
viewForSupplementaryElementOfKind kind: String,
atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
//1
switch kind {
//2
case UICollectionElementKindSectionHeader:
//3
let headerView =
collectionView.dequeueReusableSupplementaryViewOfKind(kind,
withReuseIdentifier: "FlickrPhotoHeaderView",
forIndexPath: indexPath)
as! FlickrPhotoHeaderView
headerView.label.text = searches[indexPath.section].searchTerm
return headerView
default:
//4
assert(false, "Unexpected element kind")
}
}
這個方法和cellForItemAtIndexPath相似,但卻視為額外的視圖所用的.下面是以上代碼的釋義:
- kind參數(shù)由布局對象提供并標(biāo)明是哪個額外的視圖.
- UICollectionElementKindSectionHeader為flow layout提供的一種額外視圖.通過添加storyboard中的屬性來告訴flow layout需要額外的視圖,因此添加了個section header.當(dāng)然也有UICollectionElementKindSectionFooter,但現(xiàn)在并沒有用到.如果你使用的不是flow layout,并不會這么容易就添加header和footer.
- header通過storyboard中的identifier來標(biāo)示.原理同cell相似.label上的文字被設(shè)置為相關(guān)的搜索項(xiàng)目.
- 斷言在這里是提醒后來的開發(fā)者(或者是將來的你),這里并不想得到除header view外的對象.
運(yùn)行程序,你會看到UI快完成了.如果你進(jìn)行了多個搜索,你將會得到section headers來很好地分隔你的搜索結(jié)果.翻轉(zhuǎn)設(shè)配你會發(fā)現(xiàn)無需額外的工作,各個布局都看起來很不錯.
Cell的交互
在最后,你將學(xué)到cell的一些交互.你將完成兩種不同的操作.一種是放大圖片,一種是多選圖片來分享.單個選擇
Collection能夠通過動畫來變化它的布局.第一個任務(wù)是點(diǎn)擊時圖片放大.
首先,你需要添加一個屬性來表示你點(diǎn)擊的cell.打開FlickrPhotosViewController.swift,添加如下代碼:
//1
var largePhotoIndexPath : NSIndexPath? {
didSet {
//2
var indexPaths = [NSIndexPath]()
if largePhotoIndexPath != nil {
indexPaths.append(largePhotoIndexPath!)
}
if oldValue != nil {
indexPaths.append(oldValue!)
}
//3
collectionView?.performBatchUpdates({
self.collectionView?.reloadItemsAtIndexPaths(indexPaths)
return
}){
completed in
//4
if self.largePhotoIndexPath != nil {
self.collectionView?.scrollToItemAtIndexPath(
self.largePhotoIndexPath!,
atScrollPosition: .CenteredVertically,
animated: true)
}
}
}
}
下面是代碼的釋義:
- largePhotoIndexPath為點(diǎn)擊圖片的index path.
- 當(dāng)此屬性被更新時,collection view需要更新.didSet屬性表明這是個安全的地方來處理這些更新.如果用戶已經(jīng)點(diǎn)擊了一幅圖片后再點(diǎn)擊另一幅,或者點(diǎn)擊相同一副圖兩次則需要縮放動畫,這時需要兩個cells重載.
- 更新動畫完成后,最好將放大的圖片放在屏幕中央.
"如何來增大Cell鲤拿?"待會告訴你.
點(diǎn)擊cell時會將collection view選中.你將通過設(shè)置的largeIndexPath屬性來獲取你點(diǎn)擊的cell,但你并不希望這個cell真正被選中,當(dāng)你使用多選時這將會是你迷惑.collection view 通過代理方法來判斷是否選中了一個cell.仍然在FlickrPhotosViewController.swift添加一個新的擴(kuò)展方法來增加collection view的代理方法,如下:
extension FlickrPhotosViewController : UICollectionViewDelegate {
override func collectionView(collectionView: UICollectionView,
shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
if largePhotoIndexPath == indexPath {
largePhotoIndexPath = nil
}
else {
largePhotoIndexPath = indexPath
}
return false
}
}
這個方法相當(dāng)簡單.如果這個cell已經(jīng)是大圖了,設(shè)置largePhotoIndexPath為nil,相反地,設(shè)置其index path為所點(diǎn)擊的.這將會調(diào)用之前你添加的屬性觀察者來使collection view更新重載.
要實(shí)現(xiàn)點(diǎn)擊cell方法圖片,需要修改flow layout的代理方法sizeForItemAtIndexPath.替換成一下代碼:
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let flickrPhoto = photoForIndexPath(indexPath)
// New code
if indexPath == largePhotoIndexPath {
var size = collectionView.bounds.size
size.height -= topLayoutGuide.length
size.height -= (sectionInsets.top + sectionInsets.right)
size.width -= (sectionInsets.left + sectionInsets.right)
return flickrPhoto.sizeToFillWidthOfSize(size)
}
// Previous code
if var size = flickrPhoto.thumbnail?.size {
size.width += 10
size.height += 10
return size
}
return CGSize(width: 100, height: 100)
}
添加的方法用來計(jì)算放大后的尺寸.
沒必要更大的cell除非你有更大的圖片需要真實(shí).
在Main.storyboard的collection view cell里面image view上添加一個activity indicator.將這個activity indicator 的Style設(shè)置為Large White,勾選Hides When Stopped.將它放于cell的中央,添加約束.
將activity indicator在FlickrPhotoCell.swift中添加對應(yīng)的outlet,命名為activityIndicator:
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
然后在FlickrPhotoCell.swift中添加如下代碼來控制cell的背景色:
override func awakeFromNib() {
super.awakeFromNib()
self.selected = false
}
override var selected : Bool {
didSet {
self.backgroundColor = selected ? themeColor : UIColor.blackColor()
}
}
最后需要更新FlickrPhotosCollectionViewController.swift里的cellForItemAtIndexPath:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(
reuseIdentifier, forIndexPath: indexPath) as! FlickrPhotoCell
let flickrPhoto = photoForIndexPath(indexPath)
//1
cell.activityIndicator.stopAnimating()
//2
if indexPath != largePhotoIndexPath {
cell.imageView.image = flickrPhoto.thumbnail
return cell
}
//3
if flickrPhoto.largeImage != nil {
cell.imageView.image = flickrPhoto.largeImage
return cell
}
//4
cell.imageView.image = flickrPhoto.thumbnail
cell.activityIndicator.startAnimating()
//5
flickrPhoto.loadLargeImage {
loadedFlickrPhoto, error in
//6
cell.activityIndicator.stopAnimating()
//7
if error != nil {
return
}
if loadedFlickrPhoto.largeImage == nil {
return
}
//8
if indexPath == self.largePhotoIndexPath {
if let cell = collectionView.cellForItemAtIndexPath(indexPath) as? FlickrPhotoCell {
cell.imageView.image = loadedFlickrPhoto.largeImage
}
}
}
return cell
}
下面為以上代碼的釋義:
- 總是停止activity spinner - 你需要復(fù)用cell在圖片下載之前.
- 這部分和之前相同 - 如果你沒有查看大圖片,僅設(shè)置返回thumbnail即可.
- 如果大圖片已經(jīng)加載完,返回它
- 當(dāng)你想要獲得大圖片,但還沒有時,設(shè)置spinner運(yùn)轉(zhuǎn)并通過thumbnail image獲取圖片,當(dāng)下載完后縮略圖將會被方法.
- 異步請求大圖片并通過block來返回結(jié)果.
- 加載完成后,停止spinner.
- 如果發(fā)生錯誤或沒有圖片加載,將不做什么.
- 檢查放大圖片的index path在下載時是否變化,獲得正確的cell的index path使圖片放大.
運(yùn)行程序,進(jìn)行搜索,點(diǎn)擊你喜歡的圖片,它將填滿屏幕,其它的cell將為它騰出足夠的空間.
再次點(diǎn)擊cell,或者滑動后點(diǎn)擊其它c(diǎn)ell.無需寫任何代碼,collection view會完成布局變化的動畫.
Girl學(xué)iOS100天 第14天