The core of MVVM is the bidirectional binding of data and UI. Changes in data update the UI, and changes in UI update our data. So who does this binding? Our RxSwift, of course. Since learning about the RxSwift framework, it seems that you have not really used it, so let’s take a look at the specific benefits of RxSwift.

1. Login page

First look at the effect:

The UI page code is omitted, so let’s just look at how the data UI is bound.

1. Binding UISwitch and UILabel

switch1.rx.isOn.map{! $0}.bind(to: titleLabel.rx.isHidden).disposed(by: disposeBag)
        switch1.rx.isOn.map{! $0}.bind(to: inputLabel.rx.isHidden).disposed(by: disposeBag)
Copy the code

Rx binds the value of the isOn attribute to the isHidden attribute of the label. UI changes the isOn attribute and assigns a value to the label attribute. Both attribute types are Bool.

2, UITextField and UILabel binding

nameTf.rx.text.bind(to: inputLabel.rx.text).disposed(by: disposeBag)
paswdTf.rx.text.bind(to: inputLabel.rx.text).disposed(by: disposeBag)
Copy the code

The input value text is changed, and the text attribute of the inputLabel is changed.

3. Bind the prompt text

let nameVerify = nameTf.rx.text.orEmpty.map{$0.count>5}
nameVerify.bind(to: nameLabel.rx.isHidden).disposed(by: disposeBag)
let pawdVerify = paswdTf.rx.text.orEmpty.map{$0.count>5}
pawdVerify.bind(to: paswdLabel.rx.isHidden).disposed(by: disposeBag)
Copy the code

There are often hints that need to be changed to follow the input, such as setting conditions via map to bind the sequence to the corresponding UI control to control explicit or implicit. When the input text characters are greater than 5 to hide the prompt text, the above sequence meets the conditions to send true, isHidden=true isHidden.

4. Union binding

Observable.combineLatest(nameVerify,pawdVerify){
    $0 && $1
}.bind(to: loginBtn.rx.isEnabled).disposed(by: disposeBag)
Copy the code

The combination of two user names and passwords controls whether the login button can be clicked. The combineLatest is merged into a new sequence, and both conditions hold even if the login button is available.

Through the above demonstration, we can obviously feel the convenience brought by RxSwift. Usually, we need to set triggering events and assign values to display in triggering events. The code is too long, and business and UI are scattered and difficult to manage. In RxSwift, only one or two lines of code are needed to complete the creation, monitoring and assignment of events.

UITableView list display

First look at the RxSwift implementation:

There is nothing special in the presentation. In the normal way, we need to follow the agent and implement the agent method. In RxSwift we can write as follows:

1, create tableView

tableview = UITableView.init(frame: self.view.bounds,style: .plain)
tableview.tableFooterView = UIView()
tableview.register(RowViewCell.classForCoder(), forCellReuseIdentifier: resuseID)
tableview.rowHeight = 100
self.view.addSubview(tableview)
Copy the code

As a general rule, RxSwift can’t simplify our UI any more, so we still need to build the implementation step by step. Of course here we can see that we are not following the delegate and dataSource proxies.

2. Initialize the sequence and display it

let dataOB = BehaviorSubject.init(value: self.viewModel.dataArray)
dataOB.asObserver().bind(to: tableview.rx.items(cellIdentifier:resuseID, cellType: RowViewCell.self)){(row, model, cell) in
    cell.setUIData(model as! HBModel)
}.disposed(by: disposeBag)
Copy the code

Initialize a BehaviorSuject sequence, and load the cell. At this point we are ready to present a list, and as for the cell style we will create the Settings normally. In just two steps here we have a complete list, which is very concise and very efficient.

Much like we did with the split agent implementation in OC, RxSwift helps us implement the internal methods.

3. Realize click events

tableview.rx.itemSelected.subscribe(onNext: {[weak self] (indexPath) in
    print(Click on the"\(indexPath)Line")
    self? .navigationController! .pushViewController(SectionTableview.init(), animated: true)
    self? .tableview.deselectRow(at: indexPath, animated:true)
}).disposed(by: disposeBag)
Copy the code

All click events are processed as a sequence to send click messages to the observer.

4. Delete a cell

tableview.delegate = self
tableview.rx.itemDeleted.subscribe(onNext: {[weak self] (indexPath) in
    print("Delete\(indexPath)Line")
    self! .viewModel.dataArray.remove(at: indexPath.row)self? .loadUI(obSubject: dataOB) }).disposed(by: disposeBag)extension RowTableview: UITableViewDelegate{
    func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        return .delete
    }
}
Copy the code

Here we need to follow the agent and implement the above method to set the delete type.

5. Add a cell

tableview.delegate = self
tableview.rx.itemInserted.subscribe(onNext: {[weak self] (indexPath) in
    print("Add data:\(indexPath)Line")
    guard let model = self? .viewModel.dataArray.lastelse{
        print("Data equality is not easy to add.")
        return
    }
    self? .viewModel.dataArray.insert(model, at: indexPath.row)self? .loadUI(obSubject: dataOB) }).disposed(by: disposeBag)extension RowTableview: UITableViewDelegate{
    func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        return .insert
    }
}
Copy the code

Ditto follow proxy, implementation method, set to insert type.

6. Move the cell

tableview.isEditing = true
tableview.rx.itemMoved.subscribe(onNext: {[weak self] (sourceIndex, destinationIndex) in
    print("From the\(sourceIndex)Move to the\(destinationIndex)")
    self? .viewModel.dataArray.swapAt(sourceIndex.row, destinationIndex.row)self? .loadUI(obSubject: dataOB) }).disposed(by: disposeBag)Copy the code

Setting to editable can both appear to delete the icon to go, and move the icon.

  • usetableviewRespond to the function simply by passingtableviewCall the corresponding sequence and subscribe
  • Move, addcellWe need to implementUITableViewDelegateProxy method, and set the corresponding EditingStyle
  • withcellNot the same high, also need us to achieveUITableViewDelegateTo return different heights depending on the type

Group implementation of UITableView

1. Create tableView

/ / list
tableview = UITableView.init(frame: self.view.bounds,style: .plain)
tableview.tableFooterView = UIView()
tableview.register(RowViewCell1.classForCoder(), forCellReuseIdentifier: resuseID)
tableview.rowHeight = 80
tableview.delegate = self// Here follow protocol - implement edit type delete, add, set header and tail view height
self.view.addSubview(tableview)
Copy the code
  • Set up thedelegateCan be implementedcellEdit type (delete, add) set header and tail view height

Create a Model file and declare a structure to set the properties we want to display

struct CustomData {
    let name: String
    let gitHubID: String
    var image: UIImage?
    init(name:String, gitHubID:String) {
        self.name = name
        self.gitHubID = gitHubID
        image = UIImage(named: gitHubID)
    }
}
Copy the code
  • Each piece of displayed data is retrieved from the structure

3. Create a group information structure

struct SectionOfCustomData {
    var header: Identity
    var items: [Item]}extension SectionOfCustomData: SectionModelType{
    typealias Item = CustomData
    typealias Identity = String
    
    var identity: Identity{
        return header
    }
    
    init(original: SectionOfCustomData, items: [Item]) {
        self = original
        self.items = items
    }
}
Copy the code
  • headerHeader header string
  • itemsArray structure to hold the struct objects from Step 1
  • extensionSectionOfCustomDataStructure, definitionItemforCustomDataType,IdentityforStringtype

Create a data source class and set the data

class CustomDataList {
    var dataArrayOb:Observable"[SectionOfCustomData] > {get{
            return Observable.just(dataArray)
        }
    }
    var dataArray = [
        SectionOfCustomData(header: "A", items: [
            CustomData(name: "Alex V Bush", gitHubID: "alexvbush"),
            CustomData(name: "Andrew Breckenridge", gitHubID: "AndrewSB"),
            CustomData(name: "Anton Efimenko", gitHubID: "reloni"),
            CustomData(name: "Ash Furrow", gitHubID: "ashfurrow")),SectionOfCustomData(header: "B", items: [
            CustomData(name: "Alex V Bush", gitHubID: "alexvbush"),
            CustomData(name: "Andrew Breckenridge", gitHubID: "AndrewSB"),
            CustomData(name: "Anton Efimenko", gitHubID: "reloni"),
            CustomData(name: "Ash Furrow", gitHubID: "ashfurrow")),SectionOfCustomData(header: "C", items: [
            CustomData(name: "Alex V Bush", gitHubID: "alexvbush"),
            CustomData(name: "Andrew Breckenridge", gitHubID: "AndrewSB"),
            CustomData(name: "Anton Efimenko", gitHubID: "reloni"),
            CustomData(name: "Ash Furrow", gitHubID: "ashfurrow")]),]}Copy the code
  • Create an array to hold the defined data structures and set each group of information
  • Inserts an array into the observable sequence used to send elements to the bound object

Create a data source object of type SectionOfCustomData

let dataSource = RxTableViewSectionedReloadDataSource<SectionOfCustomData>(configureCell: {[weak self] (dataSource, tableView, indexPath, HBSectionModel) - >RowViewCell1 in
    
    let cell = tableView.dequeueReusableCell(withIdentifier: self! .resuseID,for: indexPath) as! RowViewCell1
    cell.selectionStyle = .none
    cell.setSectionUIData(dataSource.sectionModels[indexPath.section].items[indexPath.row])
    return cell
})
Copy the code

Click to view the class, inside view, this class inherits TableViewSectionedDataSource class, in class, realize all the external tableview actually UITableViewDataSource agent method, through the closure property, Delegate processing in the proxy method to an external implementation.

public typealias ConfigureCell = (TableViewSectionedDataSource<Section>, UITableView.IndexPath.Item) - >UITableViewCell
public typealias TitleForHeaderInSection = (TableViewSectionedDataSource<Section>, Int) - >String?
public typealias TitleForFooterInSection = (TableViewSectionedDataSource<Section>, Int) - >String?
public typealias CanEditRowAtIndexPath = (TableViewSectionedDataSource<Section>, IndexPath) - >Bool
public typealias CanMoveRowAtIndexPath = (TableViewSectionedDataSource<Section>, IndexPath) - >Bool
Copy the code

The external implementation is as follows:

// Display the header view
dataSource.titleForHeaderInSection = {(dataSource,index) -> String in
    return dataSource.sectionModels[index].header
}
// Show the tail view
dataSource.titleForFooterInSection = {(dataSource,index) -> String in
    return "\(dataSource.sectionModels[index].header)Tail view"
}
// Set editable - Set editable depending on the group
dataSource.canEditRowAtIndexPath = {data,indexPath in
    return true
}
// Set removable - Set removable depending on the group
dataSource.canMoveRowAtIndexPath = {data,indexPath in
    return true
}
Copy the code

The effect is as follows:

Search search request implementation

There’s a search list request, enter text into the search box, make a request, and then load the data into the TableView list. UI general operations, no description. Usually we need to add an input event, send the network request in the event method, and then load the data into the TableView. In RxSwift, we don’t need to do anything complicated, we just bind the UI to the sequence, and the sequence binds to the UI.

Create the data Model class

class searchModel: HandyJSON {
    var name: String = ""
    var url:  String = ""
    required init() {}init(name:String,url:String) {
        self.name = name
        self.url  = url
    }
}
Copy the code
  • Stores properties for display and provides initialization methods
  • Inherited fromHandyJSONCan help us serialize the requested data

Create the viewModel class

class SearchViewModel: NSObject {
    //1, create a sequence
    let searchOB = BehaviorSubject(value: "")

    lazy var searchData: Driver<[searchModel]> = {
        return self.searchOB.asObservable()
            .throttle(RxTimeInterval.milliseconds(300), scheduler: MainScheduler.instance)// Set to send a message every 300 milliseconds
            .distinctUntilChanged()// Send the message only if the content of the search box changes
            .flatMapLatest(SearchViewModel.responseData)
            .asDriver(onErrorJustReturn: [])
    }()
    //2. Request data
    static func responseData(_ githubID:String) -> Observable<[searchModel]>{
        guard! githubID.isEmpty,let url = URL(string: "https://api.github.com/users/\(githubID)/repos")else{
            return Observable.just([])
        }
        return URLSession.shared.rx.json(url: url)
            .retry()// The request failed and attempted to rerequest
            .observeOn(ConcurrentDispatchQueueScheduler(qos: .background))// Background download
            .map(SearchViewModel.dataParse)
    }
    //3. Data serialization
    static func dataParse(_ json:Any) -> [searchModel]{
        // Dictionary + array
        guard let items = json as? [[String:Any]] else {return []}
        / / the serialization
        guard let result = [searchModel].deserialize(from: items) else {return []}
        return result as! [searchModel]
    }
}
Copy the code
  • To create aBehaviorSubjectSequence of type, can be sequence producer and observer
  • searchDataEnter the entry that triggers a search for network data
  • throttleSet an interval for sending messages to avoid frequent requests
  • distinctUntilChangedA message is sent only if the input changes
  • flatMapLatestThe sequence of the sequence needs to sink the request, callback the result
  • asDriverMake the sequenceDriverSequence, to ensure that state is shared, requests are not sent repeatedly, and messages are sent on the main thread

3, two-way binding

Search box bound to sequence:

self.searchBar.rx.text.orEmpty
            .bind(to: self.viewModel.searchOB).disposed(by: disposeBag)
Copy the code
  • Bind a sequence, and when input sends a message to the sequence, it begins to request data and save it

Bind the UI – > tableview:

self.viewModel.searchData.drive(self.tableview.rx.items) {[weak self] (tableview,indexPath,model) -> RowViewCell2 in
    let cell = tableview.dequeueReusableCell(withIdentifier: self! .resuseID)as! RowViewCell2
    cell.selectionStyle = .none
    cell.nameLabel.text = model.name
    cell.detailLabel.text = model.url
    return cell
}.disposed(by: disposeBag)
Copy the code
  • throughdriveSend the requested shared data and bind the data totableviewDisplayed on the

The final effect is as follows:

Through the above experience of using RxSwift, we can find that RxSwift has left out the creation of all events, such as click events, edit events, button events, etc., and it can be used wherever the UI is created. The generation of events is directly provided by RxSwift, and the display of UI can also be directly assigned to RxSwift. What we need to do is: bind the data and UI to each other.

Before getting into RAC and RxSwift, individuals have encapsulated these events for easy calls, but they haven’t thought much about data binding.