Skip to content
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Fx Studio
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Written by chuotfx on February 26, 2022

Animated Data Sources & Dynamic TableView – RxSwift

RxSwift

Contents

  • Chuẩn bị
  • Animated Data Sources
  • Setup View
  • Define Model
    • Item
    • Section
  • Create Datas
  • DataSource
  • Binding
  • Insert Item
  • Swipe to Delete
  • Move Item
  • Tạm kết

Chào mừng bạn đến với Fx Studio. Bạn đã tìm hiểu về việc sử dụng TableView với RxSwift rồi, tuy nhiên chúng vẫn là các danh sách tĩnh. Và bài viết này sẽ hướng dẫn bạn hiển thị một TableView với dữ liệu động. Tương ứng là các thao tác thêm, sửa, xóa … từ người dùng bằng đối tượng mới, đó là Animated Data Sources. Mọi thứ sẽ được thực hiển bởi RxSwift nhóe!

Nếu bạn chưa biết gì về TableView với RxSwift, thì có thể tham khảo 2 bài viết sau:

    • Display a basic TableView
    • RxDataSource & TableView with Section

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!

    • RxSwift – Phần 3 : RxCocoa Basic

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.

Animated Data Sources

Chúng ta đã làm quen với thư viện RxDataSource mà đã được sử dụng trong các bài viết trước rồi. Tuy nhiên, chúng ta mới sử dụng được một đối tượng DataSource đầu tiên. Trong phạm vi bài viết này, chúng ta sẽ sử dụng tiếp đối tượng RxTableViewSectionedAnimatedDataSource cho các TableView với dữ liệu động.

Bạn có thể áp dụng tương tự cho UICollectionView với đối tượng RxCollectionViewSectionedAnimatedDataSource

Animated Data Sources cung cấp kiểu nguồn dữ liệu đặc biệt & động cho các đối tương TableView. Kèm theo đó là các hiệu ứng mà bạn khai báo, khi diễn ra các sự kiện của người dụng:

    • Insert
    • Delete
    • Move

Về bản chất, cả 2 đối tượng DataSource của thư viện đều như nhau. Về Animated Data Sources thì cần kiểu dữ liệu cho Section phải tuân thủ AnimatableSectionModelType Protocol

Và để đảm bảo cho việc phản ứng lại với nguồi dữ liệu động, thì các khai báo model cũng phải tuân thủ 2 Protocol sau:

    • IdentifiableType Protocol
    • Equatable Protocol

Chi tiết chúng là gì, thì chúng ta sẽ tìm hiểu ở các phần sau nhóe!

Setup View

Đâu tiên, bạn cần chuẩn bị những thứ cần thiết cho việc demo trước nhóe. Chúng ta cần 1 ViewController với 1 UITableView. Bạn khai báo thêm các thuộc tính sau:

@IBOutlet weak var tableView: UITableView!

private var bag = DisposeBag()

Với bag là túi rác quốc dân, dùng để hủy các subscription phát sinh ra trong quá trình ViewController hoạt động.

Tiếp theo, bạn cần thêm các UIBarButtonItem để phục vụ cho các việc tác động vào dữ liệu nguồn. Thêm đoạn code sau vào viewDidLoad của ViewController nhóe.

let addButton = UIBarButtonItem(title: "Add", style: .done, target: self, action: #selector(addNewItem))
self.navigationItem.rightBarButtonItem = addButton
let editButton = UIBarButtonItem(title: "Edit", style: .plain, target: self, action: #selector(editItems))
self.navigationItem.leftBarButtonItem = editButton

Ta sẽ có 2 function tương ứng như sau:

@objc func addNewItem() {
    // ...
}

@objc func editItems() {
    let editMode = tableView.isEditing
    tableView.setEditing(!editMode, animated: true)
}

Với function addNewItem() ta sẽ tìm hiểu sau. Còn với editItems() thì đơn giản là kich hoạt chế độ EditMode của TableView mà thôi. Với việc kích hoạt editing thì người dùng có thể xóa và thay đổi thứ tự các cell trong TableView.

Define Model

Chúng ta sẽ tìm hiểu cách sử dụng Animated Data Sources thông qua các ví dụ code nhóe. Trước tiên, bạn cần phải định nghĩa kiểu dữ liệu mà bạn sẽ sử dụng trong Dynamic TableView của bạn.

Item

Đầu tiên, bạn cần định nghĩa struct/class dùng làm kiểu dữ liệu cho các Item trong TableView của bạn. Ta thử lấy ví dụ như sau:

struct ColorItem {
    let id = UUID()
    let color: UIColor
    
    init() {
        let red = Float.random(in: 0...1)
        let blue = Float.random(in: 0...1)
        let green = Float.random(in: 0...1)
        
        color = UIColor(red: CGFloat(red), green: CGFloat(green), blue: CGFloat(blue), alpha: 1.0)
    }
}

Theo quan sát, thì chúng không có gì đặc biệt, nên bạn sẽ vẫn sử dụng được các class/struct có sẵn trong project của bạn.

Tiếp theo, chính là bạn thêm khai báo mới cho chúng. Để nó sẽ là Model dùng được cho các đối tượng Animated Data Sources. Xem tiếp ví dụ code nhóe!

extension ColorItem: IdentifiableType, Equatable {
    typealias Identity = UUID
    
    var identity: UUID {
        return id
    }
}

Trong đó:

  • Bạn chỉ cần mở rộng các class/struct có sẵn
  • Với IdentifiableType Protocol, sẽ giúp bạn xác định được định danh của từng item là duy nhất trong TableView. Bạn cần xác định kiểu cho Identity và giá trị thuộc tính identity trả về.
  • Với Equatable Protocol, sẽ giúp bạn so sánh các item với nhau, thông qua các thuộc tính của class/struct có thể so sánh được. Áp dụng cho các trường hợp delete hay move.

Tóm lại, bạn chỉ cần quan tâm tới phần extension và xác định các thuộc tính theo yêu cầu mà thôi.

Section

Tiếp theo, bạn cần xác định kiểu dữ liệu dành cho Model đóng vai trò là Section trong Animated Data Sources nhóe. Chúng ta xem qua ví dụ sau tiếp nhóe!

struct ColorSection {
    var items: [Item]
}

extension ColorSection: AnimatableSectionModelType {
    typealias Item = ColorItem
    typealias Identity = Int
    
    init(original: ColorSection, items: [Item]) {
        self = original
        self.items = items
    }
    
    // Need to provide a unique id, only one section in our model
    var identity: Int {
        return 0
    }
    
}

Trong đó:

  • Khai báo struct ColorSection như bình thường.
  • Các khai báo dữ liệu cần cho Animated Data Sources thì sẽ thực hiện ở phần extension của struct.
  • Khai báo với việc tuân thủ AnimatableSectionModelType Protocol và khai báo 2 kiểu dữ liệu cho Item & Identity
  • Với Identity được dùng để phân biệt giữa các Section với nhau. Nó không liên quan gì tới Identity trong khai báo model Item ở trên.

Trong ví dụ, ta sẽ khai báo tiếp thuộc tính identity cho Model Section của Animated Data Sources. Với việc trả về là 0 thì xác định duy nhất 1 section được sử dụng cho toàn bộ TableView. (Thực chất là ví dụ thôi, chứ bạn có thể return gì ở đó cũng được nhóe!)

Create Datas

Sau phần khai báo kiểu dữ liệu cho các Models, chúng ta tiến hành việc tạo dữ liệu sẽ sử dụng cho Animated Data Sources. Ta sử dụng một ViewController và mọi thứ được nhét hết vào đó.

Bắt đầu, bạn cần dữ liệu chính TableView. Trước đây, chúng ta luôn sử dụng một Array để lưu trữ dữ liệu cho TableView. Với Animated Data Sources thì ta cũng vẫn sử dụng như vậy. Xem ví dụ nhóe!

private var colors: [ColorItem] = [
    ColorItem()
]

Cũng không có gì đặc biệt với colors. Tiếp theo, bạn sẽ cần 1 kiểu dữ liệu là Observable, nhưng vừa có thể phát đi các dữ liệu mới khi nguồn dữ liệu chính bị thay đổi. Từ đó, đối tượng Animated Data Sources sẽ bind lên TableView. Dẫn tới việc thay đổi của giao diện.

Đối tượng ta sử dụng sẽ là BehaviorRelay. Nó hội tụ tất cả các đặc điểm cần có:

    • Observable
    • Subject
    • Emit data

Và nó đại diện cho Section. Xem tiếp ví dụ cho cho dữ liệu của Sections nhóe!

private let sections = BehaviorRelay<[ColorSection]>(value: [])

Chúng ta bắt đầu với một TableView chưa có bất cứ dữ liệu nào hết, thì sẽ khai báo section với value là 1 array rỗng.

DataSource

Bây giờ, chúng ta sẽ đi đến nhân vật chính trong bài viết này. Đó là RxTableViewSectionedAnimatedDataSource. Xem qua ví dụ trước nhóe.

private var dataSource = RxTableViewSectionedAnimatedDataSource<ColorSection>(
    animationConfiguration: AnimationConfiguration(insertAnimation: .fade,
                                                   reloadAnimation: .fade,
                                                   deleteAnimation: .fade),
    configureCell: { (dataSource, tableView, IndexPath, item) in
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: IndexPath) as! ColorCell
        cell.titleLabel.text = item.id.uuidString
        cell.titleLabel.backgroundColor = item.color
        return cell
    }
)

Trong đó:

  • dataSource sẽ khai báo với 2 tham số chính
  • Tham số animationConfiguration khai báo cho hiệu ứng khi có sự thay đổi về nguồn dữ liệu. Trong ví dụ sẽ config cho 3 hiệu ứng chính là insert, reload & delete.
  • configureCell thì tương tự như đối tượng DataSource ở bài viết trước. Đây chính là function cellForRow trong UITableViewDelegate Protocol.

Tất nhiên, bạn cũng cần phải register cell trước cho TableView. Vì trong ví dụ trên, mình sử dụng Custom Resuable Cell nhóe!

let nib = UINib(nibName: "ColorCell", bundle: .main)
tableView.register(nib, forCellReuseIdentifier: "cell")

Đối tượng dataSource này sẽ đóng vai trò trung tâm cho toàn bộ TableView. Nó tiếp nhận dữ liệu từ nguồn phát và tiến hành cập nhật lên giao diện của TableView & các Cell.

Binding

Ta sẽ đã có nguồn dữ liệu và đã có DataSource rồi. Nhiệm vụ tiếp theo là bạn tiến hành Binding dữ liệu từ nguồn lên giao diện. Xem ví dụ code tiếp nhóe!

sections
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: bag)

Trong đó:

  • sections là nguồn phát dữ liệu
  • Ta sẽ bind dữ liệu lên thuộc tính items của TableView trong chính không gian .rx của nó
  • Với tham số được dùng chính là dataSource, chịu trách nhiệm trong việc quản lý hiện thị của TableView

Vậy, dữ liệu sẽ được phát đi như thế nào?

Để trả lời câu hỏi này, chúng sẽ sẽ chỉ cần tập trung vào sections mà thôi. Với các phần trên, chúng ta xem như đã ổn với các khai báo rồi. Các thành phần sẽ hoạt động và phản ứng theo sự thay đổi của dữ liệu nguồn.

EZ Game!

Khi bạn muốn phát đi dữ liệu cho TableView thì sẽ như sau:

self.sections.accept([ColorSection(items: colors)])

Vì sections là một Relay, nên việc phát dữ liệu đi sẽ thông qua .accept. Còn dữ liệu vẫn được lưu trữ chính tại array colors. Như vậy, bạn sẽ pháp dụng cách này cho bất cứ chỗ nào bạn muốn thay đổi dữ liệu của nguồn.

Tới đây, bạn có thể build và cảm nhận kết quả rồi nhóe!

Animated Data Sources

Insert Item

Khi mọi thứ cài đặt và khai báo đã ổn rồi. Ta sẽ đến với hành động đầu tiên. Chính ta việc thêm mới các Item cho nguồn dữ liệu bằng hành động của người dùng. Tức là thêm phần tử cho colors và sử dụng sections để phát dữ liệu mới đi.

Tại function addNewItem(), ta thêm các dòng code sau:

@objc func addNewItem() {
    colors.append(ColorItem())
    self.sections.accept([ColorSection(items: colors)])
}

Trong đó:

  • colors.append là thêm dữ liệu mới vào colors
  • sections.accept là phát dữ liệu mới đi
  • Với value như ví dụ là[ColorSection(items: colors)] , thì chỉ có 1 section mà thôi.

Hãy build và cảm nhận kết quả tiếp nhóe!

(Trên hình là mình đã bấm liên tiếp vài lần nút Add)

Swipe to Delete

Chúng ta có 2 cách xóa 1 cell trong TableView. Chúng ta sẽ tìm hiểu cách đơn giản trước. Đó chính là “Swipe to Delete”. Tức là vút qua để xóa. Để thực hiện được điều này, bạn cần phải thay đổi cấu hình của dataSource trước. Cho phép TableView có thể chấp nhận thao tác trên. Bạn chỉnh sửa theo đoạn code sau nhóe!

private var dataSource = RxTableViewSectionedAnimatedDataSource<ColorSection>(
    animationConfiguration: AnimationConfiguration(insertAnimation: .fade,
                                                   reloadAnimation: .fade,
                                                   deleteAnimation: .fade),
    configureCell: { (dataSource, tableView, IndexPath, item) in
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: IndexPath) as! ColorCell
        cell.titleLabel.text = item.id.uuidString
        cell.titleLabel.backgroundColor = item.color
        return cell
    },
    canEditRowAtIndexPath: { (dataSource, IndexPath) in
        return true
    }
)

Sau khi đã khai báo việc config cho xóa item là canEditRowAtIndexPath rồi. Bạn sẽ cần khai báo việc xóa như thế nào. Ta sẽ sử dụng tới không gian .rx của TableView.

tableView.rx.itemDeleted
    .subscribe(onNext: { indexPath in
        self.colors.remove(at: indexPath.row)
        self.sections.accept([ColorSection(items: self.colors)])
    })
    .disposed(by: bag)

Trong đó:

  • Bạn sẽ sử dụng tới itemDeleted khi TableView bị xóa đi 1 cell hay 1 item
  • Sau đó bạn tiến hành subcribe nó và tiến hành khai báo cách giải quyết trong closure
  • Công việc chỉ đơn giản là remove tại indexPath cho array colors
  • Cuối cùng, bạn sẽ dùng sections phát dữ liệu mới đi

Lúc này, DataSource sẽ xử lý việc thay đổi khi dữ liệu nguồn có sự thay đổi, kèm theo hiệu ứng. Đơn giản vậy thôi, bạn hãy build lại project & cảm nhận kết quả tiếp nhóe!

Animated Data Sources

Move Item

Cuối cùng, bạn có thể thay đổi thứ tự các cell của TableView. Kèm theo đó là sự thay đổi thứ tự dữ liệu tại nguồn. Và cũng như phần trên, bạn sẽ thêm config cho dataSource.

Nhiều bạn sẽ thắc mắc tại sao phải thay đổi thứ tự dữ liệu tại nguồn. Vì TableView với cơ chế reusable cho các cell của nó, thì sẽ ánh xạ lại đúng dữ liệu ở nguồn. Nếu bạn không thay đổi thứ tự dữ liệu ở nguồn, thì khi reload lại TableView thì kết quả sẽ hiển thị không đúng. Nặng hơn sẽ gây ra crash chương trình.

Ta tiếp tục thêm config cho phần khai báo Animated Data Sources nhóe, xem ví dụ code sau:

private var dataSource = RxTableViewSectionedAnimatedDataSource<ColorSection>(
    animationConfiguration: AnimationConfiguration(insertAnimation: .fade,
                                                   reloadAnimation: .fade,
                                                   deleteAnimation: .fade),
    configureCell: { (dataSource, tableView, IndexPath, item) in
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: IndexPath) as! ColorCell
        cell.titleLabel.text = item.id.uuidString
        cell.titleLabel.backgroundColor = item.color
        return cell
    },
    canEditRowAtIndexPath: { (dataSource, IndexPath) in
        return true
    },
    canMoveRowAtIndexPath: { _, _ in
        return true
    }
)

Với canMoveRowAtIndexPath, thì bạn có thể di chuyển các cell. Điều kiện chính là bạn phải kích hoạt trạng thái Edit Mode của TableView trước nhóe.

Và cũng để thực hiện việc move item, bạn cũng phải thêm khai báo xử lý cho itemModed của TableView tại không gian .rx. Xem ví dụ tiếp nhóe!

tableView.rx.itemMoved
    .subscribe(onNext: { indexPaths in
        let item = self.colors.remove(at: indexPaths.sourceIndex.row)
        self.colors.insert(item, at: indexPaths.destinationIndex.row)
        self.sections.accept([ColorSection(items: self.colors)])
    })
    .disposed(by: bag)

Bạn cứ tiến hành subcribe và thực hiện các thao tác bên trong closure nhóe. Nó vẫn tương tự cách delete item ở trên.

Build và cảm nhận kết quả nhóe. Ta sẽ có 2 trường hợp khi kích hoạt Edit Mode.

  • Move bình thường

  • Kích Delete Button

Animated Data Sources

Tạm kết

  • Tìm hiểu về Animated Data Sources trong xử lý & hiển thị Dynamic TableView
  • Hướng dẫn cách khai báo các Model dùng cho Section & Item
  • Tạo dữ liệu và Observable cho phần dữ liệu của TableView
  • Cấu hình DataSource cho TableView với dữ liệu động
  • Xử lý các thao tác của người tác động lên nguồn dữ liệu

 

Okay! Tới đây, mình xin kết thúc bài viết về Animated Data Source & Dynamic TableView. 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.

  • Bạn có thể checkout code tại đây.
  • Bài viết tiếp theo: (đang cập nhật)

Cảm ơn bạn đã đọc bài viết này!

FacebookTweetPinYummlyLinkedInPrintEmailShares43

Related Posts:

  • dart
    Data Type - Dart Tour
Tags: rxswift, tableview
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

Your email address will not be published. Required fields are marked *

Donate – Buy me a coffee!

Fan page

Fx Studio

Tags

Actor Advanced Swift AI api AppDistribution autolayout basic ios tutorial blog ci/cd closure collectionview combine concurrency crashlytics dart dart basic dart tour Declarative delegate deploy design pattern fabric fastlane firebase flavor flutter GCD gradients iOS MVVM optional Prompt engineering protocol Python rxswift safearea Swift Swift 5.5 SwiftData SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Role-playing vs. Persona-based Prompting
  • [Swift 6.2] Raw Identifiers – Đặt tên hàm có dấu cách, tại sao không?
  • Vibe Coding là gì?
  • Cách Đọc Sách Lập Trình Nhanh và Hiệu Quả Bằng GEN AI
  • Nỗ Lực – Hành Trình Kiến Tạo Ý Nghĩa Cuộc Sống
  • Ai Sẽ Là Người Fix Bug Khi AI Thống Trị Lập Trình?
  • Thời Đại Của “Dev Tay To” Đã Qua Chưa?
  • Prompt Engineering – Con Đường Để Trở Thành Một Nghề Nghiệp
  • Vấn đề Ảo Giác (hallucination) khi tương tác với Gen AI và cách khắc phục nó qua Prompt
  • Điều Gì Xảy Ra Nếu… Những Người Dệt Mã Trở Thành Những Người Bảo Vệ Cuối Cùng Của Sự Sáng Tạo?

You may also like:

  • Data Type - Dart Tour
    dart

Archives

  • May 2025 (2)
  • April 2025 (1)
  • March 2025 (8)
  • January 2025 (7)
  • December 2024 (4)
  • 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)

About me

Education, Mini Game, Digital Art & Life of coders
Contacts:
contacts@fxstudio.dev

Fx Studio

  • Home
  • About me
  • Contact us
  • Mail
  • Privacy Policy
  • Donate
  • Sitemap

Categories

  • Art (1)
  • Blog (44)
  • Code (11)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (102)
  • No Category (1)
  • RxSwift (37)
  • SwiftUI (80)
  • Tutorials (87)

Newsletter

Stay up to date with our latest news and posts.
Loading

    Copyright © 2025 Fx Studio - All rights reserved.