Contents
Chào mừng bạn đến với Fx Studio. Chúng ta tiếp tục phiêu lưu trong vũ trụ RxSwift. Chủ đề của bài viết này là về làm việc với Section trong UITableView. Bên cạnh việc áp dụng RxSwift & RxCocoa, thì ta sẽ áp dụng thêm RxDataSource. Với mục đích đơn giản hóa hơn nữa thao tác & quản lý dữ liệu cho TableView.
Nếu bạn đã biết về RxSwift, nhưng chưa biết về cách sử dụng TableView với RxSwift thì có thể đọc qua bài viết sau:
Còn nếu mọi việc đã ổn rồi, thì …
Bắt đầu thôi!
Chuẩn bị
Về mặt công cụ, chúng ta sử dụng các công cụ và thư viện với version đề xuất như sau:
-
- Xcode 12.x
- Swift 5.x
- RxSwift 6.x
- RxCocoa 6.x
Về kiến thức, bạn cần hiểu được các kiến thức Reactive Programming trên iOS, hay chính là RxCocoa nhóe! Và bạn cần biết cách sử dụng RxCocoa, cũng như những khái niệm cơ bản trong không gian .rx
. Tham khảo link dưới đây nhóe!
Về mặt demo, bạn cần phải tạo một iOS Project và tiến hành install RxSwift & RxCocoa cho nó thông qua CocoaPod (hoặc thứ khác vẫn được nhóe …). Giao diện thì khá đơn giản với một ViewController & TableView mà thôi.
RxDataSource
Vấn đề
Trước tiên, chúng ta cần xem lại cách tương tác với TableView bằng RxCocoa. Bạn sẽ có các function sau để sử dụng.
rx.items(dataSource:protocol<RxTableViewDataSourceType, UITableViewDataSource>) rx.items(cellIdentifier:String) rx.items(cellIdentifier:String:Cell.Type:_:) rx.items(_:_:)
Nhìn qua bạn sẽ rối não. Nhưng tiếp theo với việc triển khai với TableView, thì sẽ giống như vừa hiện đại vừa thủ công.
let data = Observable<[String]>.just(["first element", "second element", "third element"]) data.bind(to: tableView.rx.items(cellIdentifier: "Cell")) { index, model, cell in cell.textLabel?.text = model } .disposed(by: disposeBag)
Đi kèm với việc bind dữ liệu lên TableView thì bạn phải tạo và quản lý các cell cho nó. Với những bạn newbie mà tiếp cận RxSwift quá sẽ sẽ bị …
Loạn não cmnr!
Độ phức tạp sẽ càng tăng thêm khi TableView của bạn có thêm nhiều Section. Và RxDataSource sẽ là giải pháp tối ưu dành cho bạn.
Giải pháp
Đây là một trong những thư viện phổ biến trong cộng đồng RxSwift nói chung.
Bạn có thể checkout nó tại link này.
RxDataSource sử dụng cho các đối tượng UITableView & UICollectionView. Nó thay cho việc quản lý hiển thị các cell bằng DataSource Protocol trước đây. Ngoài ra, giúp cho việc sử dụng với RxCocoa thuần túy trở nên đơn giản hơn.
Với giải pháp từ một đối tượng DataSource chung nhất thì sự biến đổi của TableView sẽ đi theo.
Tương tự như với Diffable DataSource của UIKit.
Ví dụ như sau:
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, Int>>(configureCell: configureCell) Observable.just([SectionModel(model: "title", items: [1, 2, 3])]) .bind(to: tableView.rx.items(dataSource: dataSource)) .disposed(by: disposeBag)
Các cấu hình về Cell & Data cho Tableview sẽ được bạn khai báo tại một đối tượng duy nhất. Việc còn lại bind cho TableView chỉ còn đơn giản là …
Observable.just(sections) .bind(to: tableView.rx.items(dataSource: dataSource)) .disposed(by: disposeBag)
Ngoài ra, nó giúp bạn tập trung vào bản chất Reactive Programming nhiều hơn, đó là
Khai báo
Về cách cài RxDataSource cũng khá đơn giản, bạn thêm vào Podfile như sau:
pod 'RxDataSources', '~> 5.0'
Chi tiết cách dùng như thế nào thì bạn sẽ tìm hiểu các phần sau nhóe!
Import
Sau khi bạn đã tiến hành cập nhật Pod với thư viện RxDataSource. Và khi bạn muốn sử dụng nó, bạn sẽ import đầy đủ các thư viện như sau:
import RxSwift import RxCocoa import RxDataSources
Đơn giản vậy thôi! Ahihi!
Define Model & Create Data
Define Model
Như là một thực tế cuộc sống, thì dữ liệu thường sẽ phức tạp.
Bạn không thể sử dụng các kiểu dữ liệu đơn giản cho một TableView với nhiều Sections. Hoặc khi một cell của bạn lại hiểu thị nhiều thông tin.
Đi kèm với đó, mỗi thư viện lại yêu cầu bạn tạo những kiểu model phù hợp cho nó. Và RxDataSource cũng như vậy. Các đối tượng model của bạn cần phải thỏa mãn SectionModelType Protocol.
Chúng ta bắt đầu ví dụ khai báo có model đóng vai trò là Item.
struct Animal { var name: String }
Cũng khá là đơn giản cho struct Animal. Tiếp theo, bạn cần khai báo tiếp Model cho Section. Xem tiếp ví dụ như sau:
struct AnimalSection { var header: String var items: [Item] } extension AnimalSection: SectionModelType { typealias Item = Animal init(original: AnimalSection, items: [Item]) { self = original self.items = items } }
Trong đó:
- AnimalSection là model đại diện cho Section được sử dụng
- Nó phải thỏa mãn SectionModelType Protocol. Bạn có thể sử dụng lại các struct/class trước đây và
extension
thêm mà thôi. - Bạn cần khai báo với 2 thuộc tính
header
&items
Kiểu của Item sẽ được định nghĩa trong typealias
ở phần mở rộng. Và với items
sẽ là một Array với kiểu dữ liệu khai báo trong typealias
đó.
Ngoài ra, bạn có thể sử dụng một class có sẵn là SectionModel. Nó là lớp được định nghĩa riêng cho việc tạo các model sử dụng cho RxDataSource. Với ví dụ trên, ta sẽ có AnimalSection tương ứng với SectionModle<String,String>.
Create Data
Ví dụ cách khai báo SectionModleType như trên, thì bạn vẫn có thể không cần khai báo. Vì nó chứa toàn kiểu dữ liệu đơn giản. Bạn có thể sử dụng class SectionModel để tạo dữ liệu trực tiếp vẫn được.
Xem ví dụ (không liên quan tới ví dụ trên nhóe)!
let items = Observable.just([ SectionModel(model: "Mobile", items: [ "iOS", "Android", "Flutter", "ReactNative" ]), SectionModel(model: "Web", items: [ "PHP", "Ruby", "NodeJS", "Java", "Python", "Golang" ]) ])
Còn với cách khai báo các model cho SectionModel. thì bạn vẫn tạo các đối tượng dữ liệu đúng với khai báo. Tham khảo đoạn code sau luôn nhóe!
let sections = [ // section #1 AnimalSection(header: "Mammals", items: [ Animal(name: "Cats"), Animal(name: "Dogs"), Animal(name: "Pigs"), Animal(name: "Elephants"), Animal(name: "Rabbits"), Animal(name: "Tigers") ]), // section #2 AnimalSection(header: "Birds", items: [ Animal(name: "Sparrows"), Animal(name: "Hummingbirds"), Animal(name: "Pigeons"), Animal(name: "Doves"), Animal(name: "Bluebirds"), Animal(name: "Cardinal"), Animal(name: "Robin"), Animal(name: "Goldfinch"), Animal(name: "Herons"), Animal(name: "Ducks") ]) ]
Ưu điểm là nó dễ nhìn hơn mà thôi! Về bản chất nói chung thì chúng vẫn là các …
Array của Array
DataSource
Tiếp theo, chính là nhân vật chính của chúng ta. Bạn sẽ cần khai báo một đối tượng DataSource. Với RxDataSource thì sẽ cung cấp cho bạn 2 kiểu DataSource chính:
- RxTableViewSectionedAnimatedDataSource sử dụng cho TableView bình thường với nhiều Section.
- RxCollectionViewSectionedAnimatedDataSource sử dụng với TableView với dữ liệu động.
Trong phạm vi bài viết, chúng ta sẽ tập trung vào RxTableViewSectionedAnimatedDataSource. Và chúng ta sẽ khai báo như ví dụ sau:
let dataSource = RxTableViewSectionedReloadDataSource<AnimalSection> ( // cell configureCell: { dataSource, tableView, indexPath, item in let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! AnimalCell cell.nameLabel.text = item.name return cell } )
Trong đó:
- DataSource cần một kiểu dữ liệu cho Generic. Đó là model chúng ta khai báo ở phần trên.
- Bạn sẽ cung cấp 1 closure cho
configureCell
- Sử dụng một custom Resuable Cell cho TableView, là AnimalCell với identifier là
cell
. Bạn cần phảiregister cell
tương ứng như vậy nhóe!
Tại đó, bạn sẽ khai báo tương tự như function cellForRow
của UITableViewDelegate. Ngoài ra, với các khai báo thêm cho DataSource như Header & Footer cũng khá đơn giản. Bạn sẽ có 2 cách khai báo thêm
- Tại khai báo của
dataSource
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, String>> ( // for cell configureCell: { (dataSource, tableView, indexPath, item) in // ... }, // for Header titleForHeaderInSection: { dataSource, sectionIndex in return dataSource[sectionIndex].model } )
- Gán thêm các thuộc tính cho nó
dataSource.titleForFooterInSection = { dataSource, index in return "footer \(index)" }
Binding
Tiếp theo, chính là binding dữ liệu của DataSource lên TableView. Như đã nói ở trên, công việc chúng ta đã đơn giản rồi thì càng đơn giản hơn nữa. Tất cả chỉ còn như thế này nhóe!
Observable.just(sections) .bind(to: tableView.rx.items(dataSource: dataSource)) .disposed(by: bag)
Bạn cần chú ý việc register cell
cho TableView trước nhóe! Đây là điều bắt buộc rồi.
let nib = UINib(nibName: "AnimalCell", bundle: .main) tableView.register(nib, forCellReuseIdentifier: "cell")
Bây giờ, bạn có thể build project và cảm nhận kết quả trước nhóe!
Custom Header
Để hoàn thiện được TableView với các Section, thì bạn cần custom lại các Header. Tuy nhiên, RxDataSource chỉ cung cấp phương thức đơn giản và mặc định cho Header View mà thôi. Đó là
- titleForHeaderInSection
- titleForFooterInSection
Còn cao cấp hơn, bạn cần phải sử dụng thêm các function Delegate của TableView. Bắt đầu, bạn cần setDelegate
cho TableView nhóe!
tableView .rx.setDelegate(self) .disposed(by: bag)
Tiếp theo, bạn tiến hành mở rộng ViewController với UITableViewDelegate với các function cần cho Header View như sau:
extension AnimalsViewController: UITableViewDelegate { // for header func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = Bundle.main.loadNibNamed("AnimalHeader", owner: self, options: nil)?.first as! AnimalHeader headerView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50) headerView.titleLabel.text = dataSource[section].header return headerView } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 50 } // for cell func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 44 } }
Okay! tới đây bạn có thể build và cảm nhận kết quả rồi.
Vì đây là sự kết hợp của:
Truyền thống + Hiện đại + Cộng đồng
Nên khá là rối đối với các các bạn newbie, hoặc các bạn mới tiếp cận với vũ trụ RxSwift. Nhưng tất cả cũng đều xuất phát từ cơ bản mà lên. Nắm vững nền tảng thì sẽ ổn hết à. EZ game!
Tạm kết
- Tìm hiểu về RxDataSource
- Định nghĩa và tạo các dữ liệu cho SectionModel
- Khai báo và cấu hình cho DataSource
- Binding DataSource lên TableView
- Custom Header View bằng việc kết hợp với các Delegate Protocol của TableView
Okay! Tới đây, mình xin kết thúc bài viết về RxDataSource & TableView with Section. Nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.
Cảm ơn bạn đã đọc bài viết này!
Related Posts:
Written by chuotfx
Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Charles Proxy – Phần 1 : Giới thiệu, cài đặt và cấu hình
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
- Automatic Reference Counting (ARC) trong 10 phút
- Autoresizing Masks trong 10 phút
- Regular Expression (Regex) trong Swift
You may also like:
Archives
- September 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- February 2023 (1)
- January 2023 (2)
- November 2022 (2)
- October 2022 (1)
- September 2022 (5)
- August 2022 (6)
- July 2022 (7)
- June 2022 (8)
- May 2022 (5)
- April 2022 (1)
- March 2022 (3)
- February 2022 (5)
- January 2022 (4)
- December 2021 (6)
- November 2021 (8)
- October 2021 (8)
- September 2021 (8)
- August 2021 (8)
- July 2021 (9)
- June 2021 (8)
- May 2021 (7)
- April 2021 (11)
- March 2021 (12)
- February 2021 (3)
- January 2021 (3)
- December 2020 (3)
- November 2020 (9)
- October 2020 (7)
- September 2020 (17)
- August 2020 (1)
- July 2020 (3)
- June 2020 (1)
- May 2020 (2)
- April 2020 (3)
- March 2020 (20)
- February 2020 (5)
- January 2020 (2)
- December 2019 (12)
- November 2019 (12)
- October 2019 (19)
- September 2019 (17)
- August 2019 (10)