Contents
Chào mừng bạn đến với Fx Studio. Đối tượng đầu tiên chúng ta cần luyện tập với RxSwift, đó chính là UITableView. TableView là đối tượng mà bạn luôn sử dụng trong hầu hết các iOS Project. Và với RxSwift, bạn sẽ có được một cách mới rất nhanh để hiển thị một TableView & rất xịn sò nữa. Ngoài ra, bạn có thể áp dụng tương tự cho UICollectionView, vì chúng tương đồng với nhau về bản chất.
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.
Display TableView
Chúng ta sẽ bắt đầu việc thực hành với một TableView đơn giản trước nhóe. Công việc này sẽ hiển thị một danh sách đơn giản mà thôi.
Import
Đầu tiên, bạn cần import
các thư việc Rx mà bạn sẽ dùng.
import RxSwift import RxCocoa
Tiếp theo, chúng ta cần thêm các thuộc tính cho ViewController của chúng ta.
@IBOutlet weak var tableView: UITableView! private var bag = DisposeBag() private var cities = ["Hà Nội","Hải Phòng", "Vinh", "Huế", "Đà Nẵng", "Nha Trang", "Đà Lạt", "Vũng Tàu", "Hồ Chí Minh"]
Trong đó,
tableView
là Outlet của bạn để hiển thị danh sáchbag
là túi rác quốc dân huyền thoại. Nơi chưa các subscription. Nếu bạn đã quên, thì nó sẽ giúp bạn giải phóng đi các observable, khi đối tượng chứa được giải phóng.cities
là dữ liệu của chúng ta, được dùng để hiển thị lên TableView
Bind to TableView
Tiếp theo, bạn sẽ sử dụng RxSwift với vài thao tác đơn giản như sau:
override func viewDidLoad() { super.viewDidLoad() // title title = "Cities" // register cell tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") // create observable let observable = Observable.of(cities) // bind to tableview observable .bind(to: tableView.rx.items(cellIdentifier: "cell", cellType: UITableViewCell.self)) { (index, element, cell) in cell.textLabel?.text = element } .disposed(by: bag) }
Trong đó:
- Chúng ta sẽ biến đổi Array String
cities
thành một Observable, bằng toán tửof
- Vì là một Observable rồi, nên ta có thể
bind
từng phần tử lên thuộc tínhitems
trong không gian.rx
của TableView (cái này có trong RxCocoa). - Các tham số của phương thước
bind
chính làidentifier
cho cell. Nên đảm bảo việc reusable thì bạn cầnregister
cell trước.
Và chỉ đơn giản vậy thôi. Bạn hãy build thử cả cảm nhận kết quả nhóe!
Bạn sẽ bắt đầu quên đi DataSource Protocl của TableView được rồi!
Emit Data
Bạn sẽ cảm nhận ngay sự khác biệt so với cách làm truyền thống bằng các Protocol. Khi đó, ViewController của bạn sẽ chịu trách nhiệm chính trong việc phân phối dữ liệu tới các TableView.
Dữ liệu được cung cấp tại các function của UITableViewDataSource Protocol. Chúng ta sẽ return
dữ liệu tương ứng với các vị trí (indexPath) & reusable cell.
Còn với RxSwift hay Reactive Programming thì sẽ khác. Một Observable nào đó sẽ đảm nhận công việc phân phối dữ liệu tới các TableView. Và ViewController chỉ cần nắm giữ subcription của công việc đó thôi.
Observable sẽ lần lượt emit
dữ liệu đi. Chúng ta sẽ tạo một cầu nối bind
giữ Observable & TableView. Rồi từ đó mọi thứ sẽ tự động triển khai. Đối tượng nhận các dữ liệu gởi đi từ Observable, chính là các Binder từ không gian .rx
của TableView. Trong đoạn code trên là items
.
Tất cả tạo nên một thứ gọi là:
Liền mạch logic code của bạn.
Nó không phải là một kỹ thuật dễ hiểu cho các bạn newbie. Nhưng nó giúp các bạn tiếp cận nhanh hơn theo mạch code triển khai. Mà không phải suy nghĩ nhiều như mô hình Delegation với DataSource trước đây.
Handle Events
Tại phần trên, chúng ta đã không cần tới DataSource Protocol của TableView nũa rồi. Nhưng vẫn còn một phần quan trọng nữa, đó là các sự kiện người dùng tác động lên UI. Với TableView, chúng ta ít nhất cũng phải bắt được các sự kiện này. Ví dụ như:
- Selected Cell
- Move Cell
- Delete Cell
Model Selected
Chúng ta sẽ bắt lấy sự kiện đơn giản nhất nhóe. Chính là việc chọn một cell của TableView. Bạn tham khảo đoạn code sau nhóe!
tableView.rx .modelSelected(String.self) .subscribe(onNext: { element in print("Selected \(element)") }) .disposed(by: bag)
Trong đó:
- Bạn vẫn sử dụng không gian
.rx
của TableView - Observable lần này sử dụng là
modelSelected
Bạn tưởng tượng là sẽ chạm vào phần tử dữ liệu (hay gọi là model). Do đó, kiểu của model thì phải được cung cấp cho Observable. Trong ví dụ, ta sử dụng kiểu dữ liệu là String.
Cuối cùng, mọi thứ còn lại là bạn subscribe
nó mà thôi. Và ném subscription vào túi rác quốc dân. Build và cảm nhận kết quả nhóe!
Tóm lại, các cộng việc này chính là khai báo:
- Bạn khai báo dữ liệu bind cho TableView
- Bạn lắng nghe dữ liệu phát ra khi chọn vào cell
Đó là bản chất chính của Reactive Programming. Và chúng ta xem lại toàn bộ code của TableView đơn giản này.
import UIKit import RxSwift import RxCocoa class CitiesViewController: UIViewController { @IBOutlet weak var tableView: UITableView! private var bag = DisposeBag() private var cities = ["Hà Nội","Hải Phòng", "Vinh", "Huế", "Đà Nẵng", "Nha Trang", "Đà Lạt", "Vũng Tàu", "Hồ Chí Minh"] override func viewDidLoad() { super.viewDidLoad() // title title = "Cities" // register cell tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") // create observable let observable = Observable.of(cities) // bind to tableview observable .bind(to: tableView.rx.items(cellIdentifier: "cell", cellType: UITableViewCell.self)) { (index, element, cell) in cell.textLabel?.text = element } .disposed(by: bag) // selected cell tableView.rx .modelSelected(String.self) .subscribe(onNext: { element in print("Selected \(element)") }) .disposed(by: bag) } }
Bạn cũng dễ dàng thấy được 1 điều, là tất cả code đều có thể dồn vào function đầu tiên chạy mà thôi.
Item Deselected
Ở trên là dành cho Model, còn với item thì bạn cũng có các phương thức tương tự. Lần này, mình sẽ ví dụ với item nhưng sẽ là deselected
cho nó chút khác biệt nhóe.
Bạn cũng tiếp tục thêm đoạn code này vào sau:
tableView.rx .itemDeselected .subscribe(onNext: { indexPath in print("Deselected with indextPath: \(indexPath)") }) .disposed(by: bag)
Về bản chất, nó cũng tương tự cách trên. Tuy nhiên, khi bạn tiến hành subscibe
với itemDeselected
thì chúng ta sẽ có tham số là indexPath
. Đó cũng chính là IndexPath huyền thoại xuất hiện lâu nay trong TableView. Có được nó thì chúng ta có được tất cả nhóe. Bạn có thể dùng tương tác dữ liệu hay sắp xếp lại cell …
Còn để thấy sự đơn giản, thì bạn hãy build lại project và tiếp tục cảm nhận kết quả tiếp nhóe.
Event Observables
Ngoài ra, RxCocoa cũng cung cấp cho bạn kha khá các Observable hữu dụng khác cho việc quản lý các sự kiện người dùng. Ví dụ:
- modelSelected(🙂, modelDeselected(🙂, itemSelected, itemDeselected : khi tương tác với item và cell của TableView
- modelDeleted(_:) : sự kiện xóa 1 cell
- itemAccessoryButtonTapped : khi người dùng chạm vào các button Accessory của cell
- itemInserted, itemDeleted, itemMoved : các sự kiện EditMode của TableView
- willDisplayCell, didEndDisplayingCell : các cell hiển thị và mất đi, một đặc trưng của reusable trong TableView
Với chừng này thứ, thì bạn hoàn toàn có thể an tâm mà chiến đấu với RxSwift nhóe!
Reusable Cell
Chúng sẽ sẽ không sử dụng các UITableViewCell mặc định nhiều trong thực tế làm dự án. Bạn phải tự tạo một Reusable Cell của riêng bạn và dùng nó để hiển thị trên TableView.
Với RxSwift cho trường hợp này, thì cũng tương tự với Cell mặc định của UIKit. Bạn sẽ làm lại các bước như ở trên với một ViewController mới nhóe.
- Bước 1: register cell mới. Chúng ta sẽ dùng phương pháp đăng ký với file Nib cho Reusable Cell.
let nib = UINib(nibName: "WeatherCell", bundle: .main) tableView.register(nib, forCellReuseIdentifier: "cell")
- Bước 2: Vẫn là bind dữ liệu lên TableView. Nhưng lần này
cellType
sẽ là class của Reusable Cell nhóe.
func bindTableView() { let citiesObservable = Observable.of(citiesName) citiesObservable .bind(to: tableView.rx.items(cellIdentifier: "cell", cellType: WeatherCell.self)) { (index, element, cell) in cell.cityNameLabel.text = element cell.tempLable.text = "\(Int.random(in: 10...35))°C" } .disposed(by: bag) }
Với citiesName
là một String Array. Việc xử lý còn lại thì tương tự như ở phần trên. Như vậy là ổn rồi, bạn build lại ứng & cảm nhận kết quả nhóe!
Set Delegate
Chúng ta vẫn còn một protocol nữa của TableView mà bạn hay sử dụng. Đó chính là UITableViewDelegate Protocol. Các function của Delegate Protocol đóng vài trò thông báo về trạng thái & cũng phản hồi lạ các tác động của người dùng.
Bạn sẽ không cần phải lo lắng quá nhiều tới các function có trong Protocol này. Bạn vẫn có thể dùng hợp cách code truyền thống với Reactive Programming.
Bắt đầu, bạn chỉ cần khai báo thêm như sau cho đối tượng UITableView của bạn trong không gian .rx
.
tableView.rx .setDelegate(self) .disposed(by: bag)
Đây chính là cách bạn tạo nên các Proxy Delegate trong không gian .rx
. Sau khi, bạn đã có delegate
rồi. Bạn cần phải conform Protocol cho chính ViewController của bạn. Xem ví dụ sau:
extension WeathersViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { print("Did selected TableView with \(indexPath)") } }
Đơn giản như vậy. Bạn vẫn có thể sử dụng lại các function có trong Delegate Protocol, mà không cần quá lo lắng. Hãy build lại project và cảm nhận kết quả nhóe!
Multiple Cells
Vấn đề cơ bản cuối cùng là việc bạn phải sử nhiều loại cell khác nhau trong cùng một TableView. Chúng ta sẽ tiếp tục khám phá thêm cách giải quyết bài toán này nhóe!
Đầu tiên, bạn cần phải đăng ký các Resuable Cell mà bạn muốn sử dụng.
let nib1 = UINib(nibName: "WeatherCell", bundle: .main) tableView.register(nib1, forCellReuseIdentifier: "cell1") let nib2 = UINib(nibName: "WeatherWithoutStatusCell", bundle: .main) tableView.register(nib2, forCellReuseIdentifier: "cell2")
Về cách giải quyết vấn đề, thì RxSwift cũng tương đồng với cách truyền thống. Bạn cần phân biệt các đối tượng Resuable Cell tương ứng với dữ liệu.
Ví dụ như sau:
func bindTableView() { let citiesObservable = Observable.of(citiesName) citiesObservable .bind(to: tableView.rx.items) { (tableView, index, element) in let randomStatus = Bool.random() let indexPath = IndexPath(row: index, section: 0) if randomStatus { let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! WeatherCell cell.cityNameLabel.text = element cell.tempLable.text = "\(Int.random(in: 10...35))°C" return cell } else { let cell = tableView.dequeueReusableCell(withIdentifier: "cell2", for: indexPath) as! WeatherWithoutStatusCell cell.cityNameLabel.text = element cell.tempLable.text = "\(Int.random(in: 10...35))°C" return cell } } .disposed(by: bag) }
Trong đó:
- Chúng ta vẫn
bind
dữ liệu từ Observable lên TableView - Sử dụng hàm
bind
khác và không cần tới các Identifier - Trong closure bạn sẽ sử dụng lại các cell bằng việc gọi
tableView.dequeueReusableCell()
- Sau đó, bạn sẽ tìm cách phân loại các dữ liệu tương ứng với các Cell khác nhau
Hãy build lại project và cảm nhận kết quả nhóe.
EZ Game!
Tạm kết
- Hiển thị danh sách đơn giản lên TableView
- Binding dữ liệu lên TableView
- Quản lý các sự kiện người dùng
- Tích hợp với các function của Delegate Protocol
- Hiển thị nhiều loại cell khác nhau
Okay! Tới đây, mình xin kết thúc bài viết về Hiển thị TableView cơ bản với RxSwift . 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)