Tạo hiệu ứng insert và delete item trong UICollectionView
Bài đăng này đã không được cập nhật trong 6 năm
1. UICollectionView sử dụng UICollectionViewFlowLayout
Như trước đây chúng ta đã tìm hiểu về UICollectionView sử dụng UICollectionViewFlowLayout, chúng ta sẽ phải tạo 1 file Layout riêng cho collectionview. Việc này dẫn đến 1 hạn chế là khi thực hiện các action thêm mới hoặc xóa item trong collectionview sẽ khó thêm hiệu ứng animation hơn, hôm nay chúng ta sẽ tìm hiểu cách tạo ra hiệu ứng khị thêm hoặc xóa item trong collectionview.
2. Tạo collectionView
2.1 Tạo sectionHeaderView
Chúng ta sẽ tiến hành tạo 1 collectionview với nhiều section, mỗi section có 1 số item nhất định, bên phải sẽ có 1 button thêm mới, bên trái sẽ có 1 button edit cho phép chúng ta chọn item cần remove. Chúng ta cũng cần tạo section headerview cho collectionview
class Section {
var title: String?
var count = 0
}
class SectionHeader: UICollectionReusableView {
@IBOutlet private weak var flagImage: UIImageView!
@IBOutlet private weak var countLabel: UILabel!
@IBOutlet private weak var titleLabel: UILabel!
var section: Section! {
didSet {
titleLabel.text = section.title
flagImage.image = UIImage(named: section.title ?? "")
countLabel.text = "\(section.count)"
}
}
}
2.2 Tạo object Park
Data chúng ta sử dụng trong collectionview lần này sẽ là list park.
class Park {
var name: String
var state: String
var date: String
var photo: String
var index: Int
init(name: String, state: String, date: String, photo: String, index: Int) {
self.name = name
self.state = state
self.date = date
self.photo = photo
self.index = index
}
convenience init(copying park: Park) {
self.init(name: park.name, state: park.state, date: park.date, photo: park.photo, index: park.index)
}
}
2.3 Tạo datasource
Tiếp đó chúng ta tạo 1 datasource để load list park từ file text
class DataSource {
private var parks = [Park]()
private var immutableParks = [Park]()
private var sections = [String]()
var count: Int {
return parks.count
}
var numberOfSections: Int {
return sections.count
}
}
Chúng ta viết function load data từ file plist
private func loadParksFromDisk() -> [Park] {
sections.removeAll(keepingCapacity: false)
if let path = Bundle.main.path(forResource: "NationalParks", ofType: "plist") {
if let dictArray = NSArray(contentsOfFile: path) {
var nationalParks: [Park] = []
for item in dictArray {
if let dict = item as? NSDictionary {
let name = dict["name"] as! String
let state = dict["state"] as! String
let date = dict["date"] as! String
let photo = dict["photo"] as! String
let index = dict["index"] as! Int
let park = Park(name: name, state: state, date: date, photo: photo, index: index)
if !sections.contains(state) {
sections.append(state)
}
nationalParks.append(park)
}
}
return nationalParks
}
}
return []
}
Ngoài ra trong class DataSource chúng ta viết thêm 1 số hàm dùng trong viewcontroller Hàm xóa item từ list indexPath
func deleteItemsAtIndexPaths(_ indexPaths: [IndexPath]) {
var indexes = [Int]()
for indexPath in indexPaths {
indexes.append(absoluteIndexForIndexPath(indexPath))
}
var newParks = [Park]()
for (index, park) in parks.enumerated() {
if !indexes.contains(index) {
newParks.append(park)
}
}
parks = newParks
}
Hàm move item từ indexPath cũ sang indexPath mới
func moveParkAtIndexPath(_ indexPath: IndexPath, toIndexPath newIndexPath: IndexPath) {
if indexPath == newIndexPath {
return
}
let index = absoluteIndexForIndexPath(indexPath)
let nationalPark = parks[index]
nationalPark.state = sections[newIndexPath.section]
let newIndex = absoluteIndexForIndexPath(newIndexPath)
parks.remove(at: index)
parks.insert(nationalPark, at: newIndex)
}
3. Tạo UICollectionViewFlowLayout
3.1 layoutAttributesForElements
Đầu tiên chúng ta cần implement funcion tạo layout layoutAttributesForElements, trong function này chúng ta đơn giản chỉ attribute với frame và width và height x2
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var result = [UICollectionViewLayoutAttributes]()
if let attributes = super.layoutAttributesForElements(in: rect) {
for item in attributes {
let cellAttributes = item.copy() as! UICollectionViewLayoutAttributes
if item.representedElementKind == nil {
let frame = cellAttributes.frame
cellAttributes.frame = frame.insetBy(dx: 2.0, dy: 2.0)
}
result.append(cellAttributes)
}
}
return result
}
3.2 finalLayoutAttributesForDisappearingItem
Để layout có thể xử lý được những item bị remove chúng ta cần implement function finalLayoutAttributesForDisappearingItem, trong hàm này sẽ lấy list các item mà chúng ta muốn xóa, so sánh với từng item trong collectionview, nếu đúng nó sẽ trả về UICollectionViewLayoutAttributes kèm theo hiệu ứng tranform chúng ta cần
override func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let attributes = super.finalLayoutAttributesForDisappearingItem(at: itemIndexPath),
let deleted = deletedItems,
deleted.contains(itemIndexPath) else {
return nil
}
attributes.alpha = 1.0
attributes.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
attributes.zIndex = -1
return attributes
}
3.3 initialLayoutAttributesForAppearingItem
Hàm initialLayoutAttributesForAppearingItem có tác dụng tạo layout cho item khi insert
override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let attributes = super.initialLayoutAttributesForAppearingItem(at: itemIndexPath),
let added = addedItem,
added == itemIndexPath else {
return nil
}
// set new attributes
attributes.center = CGPoint(x: collectionView!.frame.width - 23.5, y: -24.5)
attributes.alpha = 1.0
attributes.transform = CGAffineTransform(scaleX: 0.15, y: 0.15)
attributes.zIndex = 50
return attributes
}
4. Xây dựng collectionview trong viewcontroller
Với số lượng item và section trong collectionview chúng ta chỉ cần implement 2 hàm
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return dataSource.numberOfSections
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSource.numberOfParksInSection(section)
}
Để tạo hiệu ứng khi insert vào collectionview chúng ta cần hàm
func addItem() {
let index = dataSource.indexPathForNewRandomPark()
let layout = collectionView?.collectionViewLayout as! FlowLayout
layout.addedItem = index
UIView.animate(withDuration: 1.0, delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: 0.0, options: [], animations: {
self.collectionView?.insertItems(at: [index])
}) { (finished) in
layout.addedItem = nil
}
}
Khi chúng ta tạo 1 item, chúng ta sẽ random index của nó dựa vào datasource và tạo 1 layout tương ứng, sau đó hàm UIView.animate sẽ tạo hiệu ứng animation khi add vào collection view. Tương tự như vậy để xóa item khỏi collection view chúng ta cần hàm
func deleteSelected() {
if let selected = collectionView?.indexPathsForSelectedItems {
let layout = collectionView?.collectionViewLayout as! FlowLayout
layout.deletedItems = selected
dataSource.deleteItemsAtIndexPaths(selected)
collectionView?.deleteItems(at: selected)
}
}
Khi chúng ta xóa item, chúng ta sẽ chọn 1 list item từ collectionview, khi nhấn xóa, indexpath tương ứng của từng item được đưa vào datasource để tiến hành xóa.
All rights reserved