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.
- use
tableview
Respond to the function simply by passingtableview
Call the corresponding sequence and subscribe- Move, add
cell
We need to implementUITableViewDelegate
Proxy method, and set the corresponding EditingStyle- with
cell
Not the same high, also need us to achieveUITableViewDelegate
To 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 the
delegate
Can be implementedcell
Edit 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
header
Header header stringitems
Array structure to hold the struct objects from Step 1- extension
SectionOfCustomData
Structure, definitionItem
forCustomData
Type,Identity
forString
type
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 from
HandyJSON
Can 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 a
BehaviorSubject
Sequence of type, can be sequence producer and observer searchData
Enter the entry that triggers a search for network datathrottle
Set an interval for sending messages to avoid frequent requestsdistinctUntilChanged
A message is sent only if the input changesflatMapLatest
The sequence of the sequence needs to sink the request, callback the resultasDriver
Make the sequenceDriver
Sequence, 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
- through
drive
Send the requested shared data and bind the data totableview
Displayed 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.