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 November 3, 2020

RxCocoa Basic – Display Data

RxSwift

Contents

  • Chuẩn bị
  • 1. Tổng quan RxCocoa
  • 2. Setup Data
    • 2.1. Create files
    • 2.2. Dummy Data
  • 3. Subscribe.
    • 3.1. Setup
    • 3.2. Subscribe
  • 4. Model to UI Control
  • 5. UI Control to Model
    • 5.1. Subscription
      • Tại sao lại là text ?
    • 5.2. Update data
    • 5.3. Test
  • Tạm kết

Chào bạn đến với Fx Studio. Bài viết này là bài viết đầu tiên cho miền đất mới trong thế giới ReactiveX. Đó là RxCocoa cho nền tảng Cocoa của iOS & MacOS. À, trong phạm vi của series thì chỉ tập trung ở iOS thôi. Và bài viết này sẽ nói về vấn đề đầu tiên cần được giải quyết, là Display Data lên giao diện.

Hiển nhiên, bạn cần phải nắm được cơ bản về Reactive Programming trong iOS với RxSwift. Thì mới tiếp tục được phần mới này. Và nếu như bạn chưa biết về chúng thì có thể tham khảo 2 phần dưới đây.

    • RxSwift – Phần 1 : Cơ bản
    • RxSwift – Phần 2 : UIKit

Còn mọi thứ ổn rồi, thì …

Bắt đầu thôi!

Chuẩn bị

    • Xcode 12
    • Swift 5.3
    • RxSwift 5.0

Phần mới này, chúng ta sẽ demo trên iOS Project. Bạn có thể tạo 1 project và tự do sáng tạo theo ý muốn riêng của bạn. Ngoài ra, bạn có thể checkout source code demo ở đây.

    • Link: checkout
    • Thư mục: /Examples/BasicRxSwift

Vì project demo của mình có rất nhiều code ở các phần khác nhau. Nên có thể bạn sẽ gặp rắc rối khi tham khảo. Do đó, bạn cũng nên xây dưng project riêng của bạn ngay từ đầu.

Về màn hình cho phần này, bạn sẽ thấy file WeatherCityViewController.swift.

  • Nó dùng để hiển thị thông tin thời tiết của một thành phố.
  • Tên thành phố là do chúng ta nhập vào.
  • Dữ liệu chúng ta sẽ lấy từ API.

Cơ bản giao diện cho ViewController thì như hình trên.

1. Tổng quan RxCocoa

Đầu tiên, bạn hãy xem qua cấu trúc thư mục của RxCocoa trước. Bạn có thể vào phần Pod project và kích để hiện ra phần RxCocoa. Nó khá là rất rất nhiều file. Hơi loạn một chút.

Vể RxCocoa, đây là phần ReactiveX cho framework Cocoa & CocoaTouch. Nó mang đầy đủ bản chất cơ bản của họ hàng nhà Rx và Reactive Programming. Chỉ sử dụng trên 2 nền tảng của Apple là iOS & MacOS. Với nó thì bạn sẽ làm được gì?

  • Thao tác trực tiếp với UIKit (iOS).
  • Cung cấp các tiện ích mở rộng cho UIKit (các extension).
  • Hỗ trợ các class/struct đặc thù cho cả Cocoa & CocoaTouch.
  • Tạo nên sự tương đồng trong các nền tảng Rx khác.
  • Có thể kết hợp cùng RxSwift để tăng cường sức mạnh.
  • Nhiều thứ hay mà bạn có thể tự làm thêm.
  • …

Mình chỉ hiện thị ra chừng đó thôi và vẫn chứ còn nhiều thứ hay lắm. Bạn hãy thử  kích vào 1 file, như là UITextField+Rx.swift xem thử có gì trong đó. Nó cũng không nhiều code lắm, tuy nhiên bạn sẽ thấy được vài thuộc tính điển hình như:

    /// Reactive wrapper for `text` property.
    public var text: ControlProperty<String?> {
        return value
    }

Bạn cũng có thể đoán được ý nghĩa của nó. Nó là thuộc tính text sẽ được Rx hoá và chúng ta sẽ dùng được nó như một Observable hoặc hơn thế nữa … vâng vâng và mây mây. Chúng ta sẽ tìm hiểu hết tất cả chúng trong các phần tiếp sau.

À, đừng sa lầy vào đây nữa. Loạn đó!

2. Setup Data

2.1. Create files

Dữ liệu là thành phần không thể nào thiếu trong một chương trình. Nó xem là máu thịt của cả hệ thống. Có nó thì các chức năng mới hoạt động được. Và cũng như bao lần trước, công việc đầu tiên cần làm là define dữ liệu.

Để chuẩn bị dữ liệu cho giao diện của chúng ta, thì cần phải thêm các file model sau:

  • Weather.swift file này sẽ chứa class/struct với các thuộc tính tương đồng với dữ liệu dùng để hiển thị lên UI. Bạn tham khảo code sau cho nó.
    • Bạn nên sử dung protocol Decoable
    • Để nó có thể map trực tiếp dữ liệu JSON từ API
    • Biến đổi data thành đối tượng một cách nhanh chóng
struct Weather: Decodable {
    let cityName: String
    let temperature: Int
    let humidity: Int
    let icon: String
    let coordinate: CLLocationCoordinate2D
}
  • WeatherAPI.swift Đây là file Model có nhiệm việc kết nối với API. Phân tích dữ liệu và trả về cho nơi nào gọi nó. Bạn tham khảo code như sau:
    • Vì class này sẽ có các đối tượng hay function liên quan tới RxSwift nên cần import RxSwift
    • Cũng vì lười nên mình 1 singleton đơn giản là 1 biến static thôi
import Foundation
import RxSwift

class WeatherAPI {
    
    // MARK: - Singleton
    static var shared = WeatherAPI()
    
    // MARK: - Properties
    
    // MARK: - init
    init() { }
    
    // MARK: - private methods
    
    // MARK: - public methods
}

2.2. Dummy Data

Chúng ta đã có 2 file Model chuẩn bị cho phần dữ liệu của ứng dụng. Để test thử cơ chế subscribe trong RxSwift hoạt động như thế nào trong project của mình. Và với việc subscribe tới nhiều UI Control cần dữ liệu từ nó. Chúng ta sẽ dùng dummy data trước. Nếu mọi việc OKE thì sẽ tiến hành connect API để lấy dữ liệu sau.

Bạn mở file WeatherAPI.swift và thêm function sau vào.

    func currentWeather(city: String) -> Observable<Weather> {
        return Observable<Weather>.just(
            Weather(cityName: "Fx Studio",
                    temperature: 99,
                    humidity: 99,
                    icon: iconNameToChar(icon: "01d"))
        )
    }

Trong đó:

  • currentWeather sẽ trả về dữ liệu cho tên city được truyền vào
  • Function sẽ return về 1 Observable với kiểu dữ liệu là Weather
  • Vẫn là toán tử huyền thoại Observable.just
  • Trong closure đó ta tạo mới 1 đối tượng Weather và gởi nó về thôi.

Bạn cần chú ý tới việc dùng iconNameToChar , thì bạn thêm function sau:

public func iconNameToChar(icon: String) -> String {
  switch icon {
  case "01d":
    return  "☀️"
  case "01n":
    return "🌙"
  case "02d":
    return "🌤"
  case "02n":
    return "🌤"
  case "03d", "03n":
    return "☁️"
  case "04d", "04n":
    return "☁️"
  case "09d", "09n":
    return "🌧"
  case "10d", "10n":
    return "🌦"
  case "11d", "11n":
    return "⛈"
  case "13d", "13n":
    return "❄️"
  case "50d", "50n":
    return "💨"
  default:
    return "E"
  }
}
  • Dó dựa theo kết quả trả về từ API mình định sẽ sử dụng sau.
  • Link mô tả mã của icon với hình thời thiết
    • http://openweathermap.org/weather-conditions
  • Có điều kiện thì bạn hay tìm hình ảnh xịn sò hơn nha. Mình dùng tạm các emoji của iOS & MacOS.

Đã tạm ổn cho setup & dummy data của Model. Và tiếp theo, chúng ta tiến hành làm công việc chính của bài hôm nay.

3. Subscribe.

3.1. Setup

Đối tượng chúng ta sẽ thao tác là trên WeatherCityViewController. Đầu tiên, bạn hãy xem qua code ban đầu của nó như sau:

import UIKit
import CoreLocation
import MapKit

class WeatherCityViewController: UIViewController {
    
    // MARK: - Outlets
    @IBOutlet private var searchCityName: UITextField!
    @IBOutlet private var tempLabel: UILabel!
    @IBOutlet private var humidityLabel: UILabel!
    @IBOutlet private var iconLabel: UILabel!
    @IBOutlet private var cityNameLabel: UILabel!
    @IBOutlet weak var containerView: UIStackView!
    
    @IBOutlet private var activityIndicator: UIActivityIndicatorView!
    
    @IBOutlet weak var locationButton: UIButton!
    
    @IBOutlet private var mapView: MKMapView!
    @IBOutlet private var mapButton: UIButton!
    
    // MARK: - Life cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        configUI()
    }
    
    // MARK: - private methods
    private func configUI() {
        title = "Weather City"
    }

}

ViewController rất đơn giản, với các UI Control cơ bản. Bạn có thể tự do sáng tạo. Mình có các UILabel để hiển thị thông tin thời tiết trên ViewController. Và cũng như bao bài viết RxSwift trước đây, để sử dụng được Rx trong ViewController này thì bạn cần import 2 thư viện Rx vào.

import RxCocoa
import RxSwift

Cái không thể thiếu là túi rác quốc dân (DisposeBag). Nó sẽ giúp bạn giải quyết rác sinh (bộ nhớ) ra do quá trình hoạt động của ViewController. Bạn sẽ yên tâm về mặt bộ nhớ khi có mặt nó trong code của bạn.

let bag = DisposeBag()

3.2. Subscribe

Tại function viewDidLoad, tiến hành subscribe nào.

override func viewDidLoad() {
        super.viewDidLoad()
        configUI()
        
        WeatherAPI.shared.currentWeather(city: "")
            .observeOn(MainScheduler.instance)
            .subscribe(onNext: { weather in
                print("City: \(weather.cityName)")
            })
            .disposed(by: bag)
    }

Quá là quen thuộc rồi. Mình sẽ lượt sơ lại cho bạn ôn bài cũ

  • WeatherAPI.shared dùng đối tượng singleton đơn giản ở trên để gọi function currentWeather
  • Giá trị trả về là 1 Observable. Nhưng ta hiểu là nó sẽ là dữ liệu từ việc connect API do đó cần phải observeOn tại MainThread.
  • Cuối cùng là disposed với bag vừa tạo.

Thử build project và xem có in ra được giá trị như chúng ta mong muốn hay không.

4. Model to UI Control

Về phương diện lập trình nói chung và lập trình iOS nói riêng. Kể cả bạn sử dụng Programming Paradigm nào đi chăng nữa. Hay bạn sử dụng bất cứ Design Patterns nào. Hoặc code một cách chân phương thuần tuý, hoặc dùng full thư viện tới tận răng … thì bản chất của lập trình điều xoay quanh việc:

Quản lý luồng dữ liệu & luồng sự kiện.

Trong phạm vi bài này, chúng ta sẽ quan tâm tới luồng dữ liệu. Chính là cách bạn đưa chúng hiển thị lên giao diện cho người dùng nhìn thấy được.

Ta có Model và đã có dữ liệu rồi. Cũng đã subscribe luôn rồi. Giờ là phần hiển thị data lên UI Control thôi. Chỉnh sửa tiếp đoạn code subscribe trên như sau:

WeatherAPI.shared.currentWeather(city: "")
            .observeOn(MainScheduler.instance)
            .subscribe(onNext: { weather in
                self.cityNameLabel.text = weather.cityName
                self.tempLabel.text = "\(weather.temperature) °C"
                self.humidityLabel.text = "\(weather.humidity) %"
                self.iconLabel.text = weather.icon
            })
            .disposed(by: bag)

Các đối tượng cityNameLabel … là các IBOutlet của WeatherCityViewController. Nếu dữ liệu nào là String thì gán trực tiếp được. Còn dữ liệu nào Int thì cần biến đổi thêm xí.

Bạn hãy build và xem kết quả đã ổn hay chưa. Bạn chú ý với tham số city trong lời gọi hàm là "" nhưng kết quả là "Fx Studio", do là dummy data mà ta đã tạo trước rồi.

Tới đây, bạn đã hoàn thành được chiều đầu tiên của công việc này.

Đưa dữ liệu hiển thị lên UI Control.

5. UI Control to Model

5.1. Subscription

Chúng ta sang chiều thứ 2, là từ giao diện tới Model.

Nhưng vẫn là chỉ là trong phạm vi luồng dữ liệu và chưa đụng gì tới luồng sự kiện.

Lần này dữ liệu sẽ là từ UI Control đưa về cho Model giải quyết. Trong bài demo này, chúng ta sử dụng một UITextField . Nó được dùng để nhập tên thành phố. Bạn mở file UITextField+Rx.swift trong không gian RxCocoa ở Pod thư mục. bạn sẽ thấy thuộc tính sau:

public var text: ControlProperty<String?> {
        return value
    }

Thuộc tính text này là 1 ControlProperty. Thực thể này khá là thú vị. Vì nó kết hợp

  • ObservableType
  • ObserverType

Tại sao lại là text ?

Vài bạn sẽ thắc mắc vì có sự kiện tác động thì mới có sự biến đổi dữ liệu. Nhưng chúng ta đang làm việc trong thế giới Rx. Việc sự kiện tác động lên UI Control sẽ làm thay đổi giá trị nào thuộc tính nào đó của nó. Tuy nhiên, với sự mở rộng thuộc tính sang không gian Rx thì nó trở thành nguồn phát (Observable). Hiển nhiên, khi có dữ liệu thì nó sẽ phát dữ liệu đi.

Chúng ta chỉ còn lắng nghe (subscription) từ chúng mà thôi.

Bạn có thể subscribe tới nó hoặc có thể thay đổi giá trị mới cho nó. Okay, chúng ta sẽ có 1 bài về em nó sau nha. Giờ sử dụng em nó nào. Tiếp tục với viewDidLoad của WeatherCityViewController, bạn thêm đoạn code sau vào:

searchCityName.rx.text.orEmpty
            .filter { !$0.isEmpty }
            .flatMap { text in
                return WeatherAPI.shared.currentWeather(city: text).catchErrorJustReturn(Weather.empty)
            }

Việc sử dụng thuộc tính text trên. Và muốn truy cập vào nó bạn hãy gõ .rx trước. Đó là không gian của Reactive có trong RxCocoa. Với ý nghĩa sau:

  • .orEmpty để đảm bảo nếu emit giá trị là nil thì chúng sẽ biến thành "".
  • Dùng .filter để lọc đi những giá trị là rỗng.
  • Cuối cùng là flatMap để biến đổi từ Text thành 1 Observable. Bằng việc gọi tới function currentWeather ở trên.

Nếu trong quá trình làm việc (gọi API) mà lỗi thì sẽ nhận được 1 Error. Tuy nhiên với catchErrorJustReturn thì bắt phát chốt và không cho nó thoát.

catchErrorJustReturn sẽ xuất hiện khi có Error trong quát trình gọi API. Mà hiện tại chúng ta đang làm dummy toàn bộ. Nên khó mà thấy được.

5.2. Update data

Tạm thời, ta về lại file Weather.swift và thêm 2 biến static mới là empty & dummy, như sau:

    static let empty = Weather(
        cityName: "Unknown",
        temperature: -1000,
        humidity: 0,
        icon: iconNameToChar(icon: "e"),
        coordinate: CLLocationCoordinate2D(latitude: 0, longitude: 0)
    )
    
    static let dummy = Weather(
        cityName: "Fx Studio",
        temperature: 99,
        humidity: 99,
        icon: iconNameToChar(icon: "01d"),
        coordinate: CLLocationCoordinate2D(latitude: 0, longitude: 0)
    )

Và tiếp tục sửa lại function currentWeather của file WeatherAPI.swift . Với việc sử dụng dữ liệu từ param.

    func currentWeather(city: String) -> Observable<Weather> {
        return Observable<Weather>.just(
            Weather(cityName: city,
                    temperature: 99,
                    humidity: 99,
                    icon: iconNameToChar(icon: "01d"),
                    coordinate: CLLocationCoordinate2D(latitude: 0, longitude: 0))
        )
    }

Quay lại file WeatherCityViewController, ta đã lấy được sự kiện gõ text từ TextField và cũng biến đổi nó thành 1 Observable<Weather> rồi. Việc tiếp theo là subscribe nó. Chỉnh sửa lại nó như sau:

        searchCityName.rx.text.orEmpty
            .filter { !$0.isEmpty }
            .flatMap { text in
                return WeatherAPI.shared.currentWeather(city: text).catchErrorJustReturn(Weather.empty)
            }
            .observeOn(MainScheduler.instance)
            .subscribe(onNext: { weather in
                self.cityNameLabel.text = weather.cityName
                self.tempLabel.text = "\(weather.temperature) °C"
                self.humidityLabel.text = "\(weather.humidity) %"
                self.iconLabel.text = weather.icon
            })
            .disposed(by: bag)

Trong đó:

UITextField —> .filter —> flatMap to Observable —> subscribe

Đó là sơ đồ mô tả cho công việc trên và nhằm giúp bạn có cái nhìn tổng quát hơn xí.

5.3. Test

Bạn hãy build ứng dụng và xem màn hình chúng ta sẽ hiển thì gì. Vì khi mới bắt đầu khởi chạy màn hình, lúc đó:

  • UITextField chưa có dữ liệu gì hết
  • Chúng ta gọi 1 lần tới function currentWeather với dữ liệu cho tham số city là ""

Nên kết quản hiển thị như hình dưới. Với Label City Name không có dữ liệu.

Giờ, bạn hãy gõ vài chữ trong UITextField, ví dụ như là Đà Nẵng. Bạn sẽ thấy Label City Name sẽ hiển thị đúng theo nhưng gì bạn gõ, với 1 cách tức thời.

Nếu bạn test và ra được kết quả đúng như mong muốn, thì chúc mừng bạn đã hoàn thành được phần đầu tiên trong RxCocoa.

 

Okay! Tới đây thì mình xin kết thúc bài viết này. 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.

Tạm kết

  • Subscribe dữ liệu & hiển thị dữ liệu lên giao diện
  • Cập nhật dữ liệu từ Model lên giao diện
  • Biến đổi dữ liệu ở Model từ việc thay đổi giá trị Control trên giao diện.

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

FacebookTweetPinYummlyLinkedInPrintEmailShares20

Related Posts:

  • Display a basic TableView - RxSwift
    Display a basic TableView - RxSwift
  • RxCocoa Basic – Delegate Proxy
    RxCocoa Basic – Delegate Proxy
  • RxCocoa Basic – Merge Observables Input
    RxCocoa Basic – Merge Observables Input
  • RxCocoa Basic – Binding Observables
    RxCocoa Basic – Binding Observables
Tags: rxswift
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 api AppDistribution Asynchronous autolayout basic ios tutorial blog callback ci/cd closure collectionview combine concurrency CoreData Core Location crashlytics darkmode dart dart basic dart tour Declarative decoding delegate deploy fabric fastlane firebase flavor flutter GCD iOS mapview MVVM optional protocol rxswift Swift Swift 5.5 SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Raw String trong 10 phút
  • Dispatch Semaphore trong 10 phút
  • Tổng kết năm 2022
  • KeyPath trong 10 phút – Swift
  • Make color App Flutter
  • Ứng dụng Flutter đầu tiên
  • Cài đặt Flutter SDK & Hello world
  • Coding Conventions – người hùng hay kẻ tội đồ?
  • Giới thiệu về Flutter
  • Tìm hiểu về ngôn ngữ lập trình Dart

You may also like:

  • RxCocoa Basic – Extend UIKit
    RxCocoa Basic – Extend UIKit
  • RxCocoa Basic – Display Data from API
    RxCocoa Basic – Display Data from API
  • Declaring Data - SwiftUI Notes #12
    Declaring Data - SwiftUI Notes #12
  • RxCocoa Basic – Binding Observables
    RxCocoa Basic – Binding Observables
  • RxCocoa Basic – Merge Observables Input
    RxCocoa Basic – Merge Observables Input

Archives

  • 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 (22)
  • Code (4)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (86)
  • RxSwift (37)
  • SwiftUI (76)
  • Tutorials (70)

Newsletter

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

    Copyright © 2023 Fx Studio - All rights reserved.

    Share this ArticleLike this article? Email it to a friend!

    Email sent!