Tùy biến layout của UICollectionView
Bài đăng này đã không được cập nhật trong 3 năm
Tùy biến layout của UICollectionView
UICollectionView là một trong những đối tượng quen thuộc đối với lập trình viên iOS.
Trước hết ta tự đặt ra câu hỏi "Vì sao cần phải tùy biến bố cục của UICollectionView". Mặc dù bản thân UIKIT của iOS đã cung cấp sẵn flow layout giúp hiển thị các đối tượng thành phần dưới dạng lưới (grid layout), nhưng trong một số trường hợp cách hiển thị này không còn phù hợp.
Ví dụ: Ta muốn hiện thị một galery ảnh nhưng các ảnh lại có kích thước khác nhau, nếu sử dụng flow layout mặc định ta sẽ có kết quả như dưới đây Giao diện trên trông "lem nhem" và không ổn chút nào.
Mục tiêu của bài viết tùy biến layout của UICollectionView để hiện thị dưới dạng Pinterest layout (một kiểu layout sử dụng phổ biến tại một số ứng dụng di động như Pinterest, Lozi, Foody, Mua chung ....)
Mô tả các đối tượng sẽ sử dụng
Các đối tượng cha
Class / protocol name | Mô tả |
---|---|
UICollectionView | Main UI component class |
UICollectionViewCell | UI component để hiển thị thành phần dưới dạng cell |
UICollectionViewDelegate | Delegate tương tác với CollectionView |
UICollectionViewDataSource | Protocol định nghĩa cách thức hiển thị dữ liệu trong CollectionView |
UICollectionViewLayout | Abtract class mô tả cách thức xử lý layout |
UICollectionViewLayoutAttributes | Đối tượng lưu trữ thông tin layout |
Các đối tượng mở rộng
Class / protocol name | Lớp cha | Mô tả |
---|---|---|
PinterestViewController | UICollectionViewController | Lớp quản lý UICollectionView |
MyCell | UICollectionViewCell | Custom Cell |
PinterestLayoutDelegate | Protocol cho phép lấy một số thông tin của CollectionView | |
PinterestLayout | UICollectionViewLayout | Định nghĩa lại cách thức tính toán kích thước, sắp xếp các cell trong collection view |
Tùy biến cho Pinterest Layout
Custom lớp UICollectionViewLayout (PinterestLayout.swift)
Mô tả
- Kế thừa lớp cha UICollectionViewLayout
- Các xử lý chính:
- Tính toán kích thước và vị trí của các cell trong CollectionView và lưu trữ vào cache
- Sử dụng cache (tọa độ, kích thước của các cell) để xác định các cell sẽ được hiển thị trong khung hình hiển thị
- Tính toán content size của CollectionView
Hàm prepare
Đây là hàm được gọi đầu tiên khi bắt đầu xử lý layout của CollectionView, hàm sẽ tiến hành xử lý những công việc sau:
- Tính toán vị trị và kích thước của các cell trong CollectionView
- Tính toán content size của CollectionView
override func prepare()
{
super.prepare()
self.attributeArray.removeAllObjects()
let numberOfColumn : Int = self.delegate.getNumberOfColumn();
let padding:CGFloat = 15.0;
let collectionViewWidth = self.collectionView?.frame.size.width
let itemWidth : CGFloat = (collectionViewWidth! - padding * CGFloat((numberOfColumn + 1))) / CGFloat(numberOfColumn)
var contentHeight:CGFloat = 0.0;
var columnArray = [CGFloat](repeating: 0.0, count: numberOfColumn);
//Tính toán kích thước và vị trí của từng cell trong CollectionView
for i in 0 ... (self.collectionView?.numberOfItems(inSection: 0))! - 1 {
var tempX : CGFloat = 0.0
var tempY : CGFloat = 0.0
let indexPath = NSIndexPath(item: i, section: 0)
let itemHeight:CGFloat = delegate.collectionView(collectionView: (self.collectionView)!, heightForPhotoAtIndexPath: indexPath)
//Tìm cột có độ dài ngắn nhất trong CollectionView
var minHeight:CGFloat = 0.0;
var minIndex:Int = 0;
if (numberOfColumn > 0){
minHeight = columnArray[0]
}
for colIndex in 0..<numberOfColumn {
if (minHeight > columnArray[colIndex]){
minHeight = columnArray[colIndex]
minIndex = colIndex
}
}
//Bổ sung cell mới vào cột có kích thước ngắn nhất
tempX = padding + (itemWidth + padding) * CGFloat(minIndex);
tempY = minHeight + padding;
columnArray[minIndex] = tempY + itemHeight;
let attributes = UICollectionViewLayoutAttributes(forCellWith:indexPath as IndexPath);
attributes.frame = CGRect(x: tempX, y: tempY, width: itemWidth, height: itemHeight);
self.attributeArray.setObject(attributes, forKey: indexPath)
//Tính toán lại chiều cao Content Size của CollectionView
let newContentHeight:CGFloat = tempY + padding + itemHeight + padding;
if (newContentHeight > contentHeight){
contentHeight = newContentHeight;
}
}
self.contentSize = CGSize(width: (self.collectionView?.frame.size.width)!, height: contentHeight);
}
Hàm collectionViewContentSize
Hàm được gọi khi cần lấy content size của CollectionView
override var collectionViewContentSize: CGSize{
// Trả về ContentSize của CollectionView đã tính được ở hàm prepare
return self.contentSize
}
layoutAttributesForElementsInRect:
Hàm được sử dụng để xác định các cell sẽ được hiển trên khung nhìn của CollectionView khi được scroll tới. Việc xác định các cell sẽ được hiển thị dựa vào cache đã được tính toán ở hàm prepare
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
// Duyệt các đối tượng trong attributeArray để tìm ra các cell nằm trong khung nhìn rect
for attributes in self.attributeArray {
if (attributes.value as! UICollectionViewLayoutAttributes).frame.intersects(rect ) {
layoutAttributes.append((attributes.value as! UICollectionViewLayoutAttributes))
}
}
return layoutAttributes
}
Sử dụng Pinterest layout cho CollectionView
Khởi tạo Collection View
let pinterestLayout = PinterestLayout()
pinterestLayout.delegate = self
self.collectionView?.collectionViewLayout = pinterestLayout
Implement Delegate
Để sử dụng Pinterest Layout đã xây dựng ở trên ta cần phải implement các delegate sau:
UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//Chỉ ra số cell sẽ có trong CollectionView (quy tắc này do code trong phần layout sử dụng hàm này để lấy ra số cell trong collection view)
return self.imageList.count
}
PinterestLayoutDelegate
//Tính toán chiều cao của từng cell
func collectionView(collectionView:UICollectionView, heightForPhotoAtIndexPath indexPath:NSIndexPath) -> CGFloat
{
let paddingSpace = self.sectionInsets.left * CGFloat(self.itemsPerRow + 1)
let availableWidth = self.collectionView.frame.width - paddingSpace
let widthPerItem = availableWidth / CGFloat(itemsPerRow)
let boundingRect = CGRect(x: 0, y: 0, width: widthPerItem, height: CGFloat.greatestFiniteMagnitude);
let rect = AVMakeRect(aspectRatio: (UIImage(named: imageList[indexPath.item])?.size)!, insideRect: boundingRect);
return rect.height
}
//Chỉ ra số cột hiện thị trong một cell
func getNumberOfColumn() -> Int {
return self.itemsPerRow
}
Mã nguồn của chương trình: https://github.com/TuInh/Report_T2_2017 Nguồn tham khảo:
All rights reserved