In this post, we will see interactor work and entity engagement with it. Currently we have not attached it with any kind of data, now we will load up some to data and try to show it in view which is all the way back to presenter.
Use Case: Now we know about view and presenter, it’s time for Interactor. We want to see name of the person and the company in UIView. For that we have one JSON file which we will load.
Task: Implement interactor, implement file reader, read the file, convert data in to model, transform it as per view and show the details.
Anticipated output:
Here is a JSON file as of now which we want to load and show the data.
Now Here is an interactor class:
Here is a file reader class:
To transform data in to model we have one company-model class:
There are some changes which we have to do in order to show the data. In presenter class, we will replace “foundList” method with below code:
Now we want to show name, in view It is divided in two partition First name and Last name.We will add one method which can return these two in a single entity. To do so we will add one function in a model class.
Now here is a code for UIView:
At this point of time there is no major code in presenter, now we will change the requirement and see how presenter could be used.
Use Case: Now we know about view and presenter, it’s time for Interactor. We want to see name of the person and the company in UIView. For that we have one JSON file which we will load.
Task: Implement interactor, implement file reader, read the file, convert data in to model, transform it as per view and show the details.
Anticipated output:
Here is a JSON file as of now which we want to load and show the data.
//JSON file
[{
"_id": "58f9a19680685a4e1e6d5016",
"index": 0,
"guid": "0b10fe86-a222-4ca5-a094-0082b7d4c1af",
"isActive": false,
"balance": "$2,107.15",
"picture": "http://placehold.it/32x32",
"age": 35,
"eyeColor": "green",
"name": {
"first": "Bertha",
"last": "Irwin"
},
"company": "COMSTAR",
"email": "bertha.irwin@comstar.net",
"phone": "+1 (806) 584-3949",
"address": "303 Vermont Street, Concho, Arizona, 1707",
"favoriteFruit": "banana"
},
{
"_id": "58f9a196c020af14f6726d3a",
"index": 1,
"guid": "d1695e0f-2d05-465e-8e14-eca9ad64c426",
"isActive": false,
"balance": "$1,194.79",
"picture": "http://placehold.it/32x32",
"age": 30,
"eyeColor": "green",
"name": {
"first": "Norton",
"last": "Curtis"
},
"company": "RECRISYS",
"email": "norton.curtis@recrisys.tv",
"phone": "+1 (850) 571-3013",
"address": "106 Bliss Terrace, Vandiver, Mississippi, 9292",
"favoriteFruit": "strawberry"
},
{
"_id": "58f9a196d46f3d10560759bd",
"index": 2,
"guid": "bf810ee3-3bc4-44e0-8056-3ac3815d2887",
"isActive": true,
"balance": "$2,259.39",
"picture": "http://placehold.it/32x32",
"age": 38,
"eyeColor": "brown",
"name": {
"first": "Zamora",
"last": "Weber"
},
"company": "PERMADYNE",
"email": "zamora.weber@permadyne.co.uk",
"phone": "+1 (929) 591-2437",
"address": "787 Crystal Street, Williston, Wisconsin, 6408",
"favoriteFruit": "strawberry"
},
{
"_id": "58f9a196fe6014ff5fd07cb5",
"index": 3,
"guid": "e5cfcd2e-2911-4293-9a0f-3a53374ea1d9",
"isActive": true,
"balance": "$3,019.42",
"picture": "http://placehold.it/32x32",
"age": 25,
"eyeColor": "blue",
"name": {
"first": "Barnes",
"last": "Pickett"
},
"company": "RECRISYS",
"email": "barnes.pickett@RECRISYS.info",
"phone": "+1 (804) 532-3235",
"address": "500 Milton Street, Nadine, Kansas, 8781",
"favoriteFruit": "apple"
},
{
"_id": "58f9a196361f66d65c9b39f8",
"index": 4,
"guid": "80ca8651-5edf-4d11-bb09-688f654ff3ad",
"isActive": true,
"balance": "$3,334.51",
"picture": "http://placehold.it/32x32",
"age": 33,
"eyeColor": "brown",
"name": {
"first": "Cohen",
"last": "James"
},
"company": "COMSTAR",
"email": "cohen.james@comvene.name",
"phone": "+1 (953) 454-3801",
"address": "230 Lefferts Avenue, Navarre, Tennessee, 3443",
"favoriteFruit": "strawberry"
},
{
"_id": "58f9a19607f1cc3aafc21c5b",
"index": 5,
"guid": "df311b09-081c-4a31-b30f-a87700728244",
"isActive": false,
"balance": "$2,046.56",
"picture": "http://placehold.it/32x32",
"age": 32,
"eyeColor": "green",
"name": {
"first": "Vanessa",
"last": "Sanchez"
},
"company": "COMSTAR",
"email": "vanessa.sanchez@magmina.biz",
"phone": "+1 (877) 430-2487",
"address": "665 Norfolk Street, Bawcomville, Kentucky, 8056",
"favoriteFruit": "banana"
}
]
Now Here is an interactor class:
class Interactor: NSObject {
func findListData() {
//Find data
let file = FileReader();
//We can set default argument in parms it self - a Swift feature
// file.readJsonFile(name:"company", exten: "json")
if let data = file.readJsonFile(name:"company") {
let myArr:NSArray? = ParseData(aObject: data);
if myArr != nil {
presenter?.foundList(aList: myArr);
}
}
}
//MARK: Model parse
func ParseData(aObject:Any) -> NSArray? {
let myArr : NSMutableArray = NSMutableArray();
if aObject is NSArray {
for mydata in aObject as! NSArray {
let data = mydata as! NSDictionary
let mymodel : CompanyModel = CompanyModel.init(fname: data.value(forKeyPath:"name.first") as! String, lname: data.value(forKeyPath:"name.last") as! String, company: data.value(forKey:"company") as! String)
myArr.add(mymodel);
}
return myArr;
}
return nil;
}
}
Here is a file reader class:
class FileReader:NSObject {
func readJsonFile(name:String, exten:String="json") -> Any? {
do {
if let file = Bundle.main.url(forResource:name, withExtension:exten) {
let data = try Data(contentsOf: file)
let json = try JSONSerialization.jsonObject(with: data, options: [])
if let object = json as? [String: Any] {
// json is a dictionary
// print(object)
} else if let object = json as? [Any] {
// json is an array
// print(object)
return object;
} else {
print("JSON is invalid")
}
} else {
print("no file")
}
} catch {
print(error.localizedDescription)
}
return nil;
}
}
To transform data in to model we have one company-model class:
class CompanyModel : NSObject {
var fName : String = ""
var lName : String = ""
var company : String = ""
init(fname:String,lname:String,company:String) {
self.fName = fname
self.lName = lname
self.company = company
}
}
There are some changes which we have to do in order to show the data. In presenter class, we will replace “foundList” method with below code:
func foundList(aList:NSArray?) {
if(aList != nil) {
userInterface?.reloadList(alist: aList)
}
else {
userInterface?.showNoContenctView()
}
}
Now we want to show name, in view It is divided in two partition First name and Last name.We will add one method which can return these two in a single entity. To do so we will add one function in a model class.
var name : String { get {
return "\(fName) \(lName)"
}
}
Now here is a code for UIView:
extension ViewController: UITableViewDataSource,UITableViewDelegate {
//MARK:- TableView methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myArr!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: ListEntryCellIdentifier, for: indexPath as IndexPath) as! listCell
let myData:CompanyModel = myArr!.object(at: indexPath.row) as! CompanyModel
cell.title.text = String(myData.name)
cell.companyName.text = String(myData.company)
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
}
//MARK: ViewInterface implementation
func reloadList(alist:NSArray?) {
myArr = alist
if myArr != nil {
DispatchQueue.main.async {
self.listTableView?.reloadData();
}
}
}
func showNoContenctView() {
let alert = UIAlertController.init(title: "VIPER", message: "No content to show", preferredStyle:.alert)
let okAction = UIAlertAction.init(title: "OK", style: UIAlertActionStyle.default, handler: nil)
alert.addAction(okAction)
self.present(alert, animated:true, completion: nil)
}
}
At this point of time there is no major code in presenter, now we will change the requirement and see how presenter could be used.
Time for twist: We want to sort the data and show the list as per their company and want to show the balance.
Anticipated output:
To do so we need to alter data in model, presenter and UIView:
class model code change:
Here is a change in code of presenter:
Now here is a code for UIView:
Anticipated output:
To do so we need to alter data in model, presenter and UIView:
class model code change:
init(fname:String,lname:String,company:String,balance:String) {
self.fName = fname
self.lName = lname
self.company = company
self.balance = balance
}
Here is a change in code of presenter:
func foundList(aList:NSArray?) {
let sortedArray:NSArray? = self.SortCompanyList(aList: aList)
if(sortedArray != nil) {
userInterface?.reloadList(alist: sortedArray);
} else {
userInterface?.showNoContenctView()
}
}
//This function is used to sort the data as we require.
func SortCompanyList(aList:NSArray?) -> NSArray? {
let sortedArray: NSMutableArray = []
if(aList != nil) {
let listOfCompany:NSArray? = aList?.value(forKeyPath:"@distinctUnionOfObjects.company") as? NSArray
if(listOfCompany != nil) {
for companyName in listOfCompany! {
let predicate = NSPredicate(format: "%K = %@", "company", companyName as! NSObject)
let filteredContacts = aList?.filtered(using: predicate)
if ((filteredContacts?.count)! > 0) {
let sortedDict = NSDictionary.init(object: filteredContacts!, forKey:companyName as! NSCopying)
sortedArray.add(sortedDict)
}
}
}
}
return (sortedArray.count > 0) ? sortedArray : nil
}
Now here is a code for UIView:
extension ViewController: UITableViewDataSource,UITableViewDelegate{
//MARK:- TableView methods
func numberOfSections(in tableView: UITableView) -> Int {
return myArr!.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let dictSection:NSDictionary = (myArr?.object(at:section) as! NSDictionary)
return (dictSection.object(forKey:dictSection.allKeys.first!) as! NSArray).count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let dictSection:NSDictionary = (myArr?.object(at:section) as! NSDictionary)
let title:String = " \(dictSection.allKeys.first as! String)"
return title
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cListEntryCellIdentifier, for: indexPath as IndexPath) as! listCell
//Fetch model
let dictSection:NSDictionary = (myArr?.object(at: indexPath.section) as! NSDictionary)
let myData:CompanyModel = (dictSection.object(forKey:dictSection.allKeys.first!) as! NSArray).object(at: indexPath.row) as! CompanyModel
//Set model data
cell.title.text = String(myData.name)
cell.companyName.text = String(myData.balance)
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
}
//MARK: ViewInterface implementation
func reloadList(alist:NSArray?) {
myArr = alist;
if myArr != nil {
DispatchQueue.main.async {
self.listTableView?.reloadData();
}
}
}
func showNoContenctView(){
let alert = UIAlertController.init(title: "VIPER", message: "No content to show", preferredStyle:.alert)
let okAction = UIAlertAction.init(title: "OK", style: UIAlertActionStyle.default, handler: nil)
alert.addAction(okAction)
self.present(alert, animated:true, completion: nil)
}
}
This is the end of second VIPER post, In next post we will explore router/wireframe code and unit test case for the same app.
No comments:
Post a Comment
Please do not spam.