Saturday, 2 November 2019

TableView Cell Expand and collapse with out any library in Swift


JsonFile (parse json data)

 [

  {
  "name": "rings",
  "sub_category": [
                   {
    
                   "name": "engagement",
                   "display_name": "Engagement"
                   },
                   {

                   "name": "band-couple band",
                   "display_name": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed scelerisque aliquet arcu, sed placerat turpis. Sed vulputate finibus nisi, nec gravida turpis consectetur quis. Nullam quis vestibulum ex. Integer lacinia quam sed rutrum tempus. Sed quis metus mollis, euismod ipsum in, vulputate turpis. Fusce suscipit ligula in efficitur interdum. Sed ante velit, vulputate nec nibh commodo, tempus molestie eros. Etiam consequat enim nisi, a mollis nisi gravida et. Fusce scelerisque ex vitae turpis fermentum facilisis."
                   },
                   {

                   "name": "Navaratnam+Collection",
                   "display_name": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed scelerisque aliquet arcu, sed placerat turpis."
                   },
                   {

                   "name": "cocktail",
                   "display_name": "Lorem ipsum dolor sit amet"
                   },
                   {

                   "name": "below+15000",
                   "display_name": "Under 15,000/-"
                   },
                   {

                   "name": "hearts",
                   "display_name": "Heart Rings"
                   },
                   {

                   "name": "halo",
                   "display_name": "Halo Rings"
                   },
                   {
                   "name": "view all",
                   "display_name": "View All"
                   }
                   ]
  },
  {
  "name": "earrings",
  "sub_category": [
                   {

                   "name": "studs",
                   "display_name": "Studs"
                   },
                   {
                   "name": "drops",
                   "display_name": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed scelerisque aliquet arcu, sed placerat turpis. Sed vulputate finibus nisi, nec gravida turpis consectetur quis. Nullam quis vestibulum ex. Integer lacinia quam sed rutrum tempus. Sed quis metus mollis, euismod ipsum in, vulputate turpis. Fusce suscipit ligula in efficitur interdum. Sed ante velit, vulputate nec nibh commodo, tempus molestie eros. Etiam consequat enim nisi, a mollis nisi gravida et. Fusce scelerisque ex vitae turpis fermentum facilisis."
                   },
                   {

                   "name": "hoops",
                   "display_name": "Hoops"
                   },
                   {
                   "name": "jhumki",
                   "display_name": "Jhumkis"
                   },
                   {
                   "name": "sui dhaga",
                   "display_name": "Sui-dhaga"
                   },
                   {
                   "name": "pearl",
                   "display_name": "Pearl"
                   },
                   {
                   "name": "below 15000",
                   "display_name": "Less Than 15,000/-"
                   },
                   {
                   "name": "view all",
                   "display_name": "View All"
                   }
                   ]
  },
  {
  "name": "pendants",
  "sub_category": [
                   {
                   "name": "alphabet",
                   "display_name": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed scelerisque aliquet arcu, sed placerat turpis."
                   },
                   {
                   "name": "religious",
                   "display_name": "Religious"
                   },
                   {
                   "name": "hearts",
                   "display_name": "Hearts"
                   },
                   {
                   "name": "diamond and colored stones",
                   "display_name": "Gemstone Pendants"
                   },
                   {
                   "name": "single stone-only diamond",
                   "display_name": "Single Diamond Pendants"
                   },
                   {
                   "name": "floral",
                   "display_name": "Floral"
                   },
                   {
                   "name": "locket",
                   "display_name": "Lockets"
                   },
                   {
                   "name": "view all",
                   "display_name": "View All"
                   }
                   ]
  },
  {
  "name": "bangles",
  "sub_category": [
                   {
                   "name": "round bangle",
                   "display_name": "Round"
                   },
                   {
                   "name": "oval bangle",
                   "display_name": "Oval"
                   },
                   {
                   "name": "eternity",
                   "display_name": "Eternity"
                   },
                   {
                   "name": "yellow gold",
                   "display_name": "Gold"
                   },
                   {
                   "name": "wedding",
                   "display_name": "Bridal"
                   },
                   {
                   "name": "view all",
                   "display_name": "View All"
                   }
                   ]
  },
  {
  "name": "bracelets",
  "sub_category": [
                   {
                   "name": "yellow gold",
                   "display_name": "Gold"
                   },
                   {
                   "name": "only diamond",
                   "display_name": "Diamond"
                   },
                   {
                   "name": "floral",
                   "display_name": "Floral"
                   },
                   {
                   "name": "view all",
                   "display_name": "View All"
                   }
                   ]
  },
  {
  "name": "nose pins",
  "sub_category": [
                   {
                   "name": "yellow gold",
                   "display_name": "Gold"
                   },
                   {
                   "name": "only diamond",
                   "display_name": "Diamond"
                   },
                   {
                   "name": "only coloured stone",
                   "display_name": "Gem Stone"
                   },
                   {
                   "name": "view all",
                   "display_name": "View All"
                   }
                   ]
  },
  {
  "name": "necklaces",
  "sub_category": [
                   {
                   "name": "yellow gold",
                   "display_name": "Gold"
                   },
                   {
                   "name": "wedding",
                   "display_name": "Bridal"
                   },
                   {
                   "name": "view all",
                   "display_name": "View All"
                   }
                   ]
  },
  {
  "name": "tanmaniya",
  "sub_category": [
                   {
                   "name": "only diamond",
                   "display_name": "Diamond"
                   },
                   {
                   "name": "ruby",
                   "display_name": "Ruby"
                   },
                   {
                   "name": "below 50000",
                   "display_name": "Under 50,000/-"
                   },
                   {
                   "name": "view all",
                   "display_name": "View All"
                   }
                   ]
  },
  {
  "name": "only diamond",
  "sub_category": [
                   {
                   "name": "rings",
                   "display_name": "Rings"
                   },
                   {
                   "name": "earrings",
                   "display_name": "Earrings"
                   },
                   {
                   "name": "pendants",
                   "display_name": "Pendants"
                   },
                   {
                   "name": "bangles",
                   "display_name": "Bangles"
                   },
                   {
                   "name": "bracelets",
                   "display_name": "Bracelets"
                   },
                   {
                   "name": "necklaces",
                   "display_name": "Necklaces"
                   },
                   {
                   "name": "nose pins",
                   "display_name": "Nose Pins"
                   },
                   {
                   "name": "view all",
                   "display_name": "View All"
                   }
                   ]
  },
  {
  "name": "plain gold jewellery",
  "sub_category": [
                   {
                   "name": "rings",
                   "display_name": "Rings"
                   },
                   {
                   "name": "earrings",
                   "display_name": "Earrings"
                   },
                   {
                   "name": "pendants",
                   "display_name": "Pendants"
                   },
                   {
                   "name": "bangles",
                   "display_name": "Bangles"
                   },
                   {
                   "name": "bracelets",
                   "display_name": "Bracelets"
                   },
                   {
                   "name": "necklaces",
                   "display_name": "Necklaces"
                   },
                   {
                   "name": "nose pins",
                   "display_name": "Nose Pins"
                   },
                   {
                   "name": "view all",
                   "display_name": "View All"
                   }
                   ]
  },
  {
  "name": "diamond and colored stones",
  "sub_category": [
                   {
                   "name": "rings",
                   "display_name": "Rings"
                   },
                   {
                   "name": "earrings",
                   "display_name": "Earrings"
                   },
                   {
                   "name": "pendants",
                   "display_name": "Pendants"
                   },
                   {
                   "name": "bangles",
                   "display_name": "Bangles"
                   },
                   {
                   "name": "bracelets",
                   "display_name": "Bracelets"
                   },
                   {
                   "name": "necklaces",
                   "display_name": "Necklaces"
                   },
                   {
                   "name": "nosepins",
                   "display_name": "Nosepins"
                   },
                   {
                   "name": "tanmaniyas",
                   "display_name": "Tanmaniyas"
                   },
                   {
                   "name": "view all",
                   "display_name": "View All"
                   }
                   ]
  },
  {
  "name": "platinum",
  "sub_category": [
                   {
                   "name": "single stone",
                   "display_name": "Single Stone"
                   },
                   {
                   "name": "multistone",
                   "display_name": "Multi Stone"
                   },
                   {
                   "name": "single stone",
                   "display_name": "Single Stone"
                   },
                   {
                   "name": "view all",
                   "display_name": "View All"
                   }
                   ]
  },
  {
  "name": "men",
  "sub_category": [
                   {
                   "name": "rings",
                   "display_name": "Rings"
                   },
                   {
                   "name": "pendants",
                   "display_name": "Pendants"
                   },
                   {
                   "name": "bracelets",
                   "display_name": "Bracelets"
                   },
                   {
                   "name": "earrings",
                   "display_name": "Earrings"
                   },
                   {
                   "name": "view all",
                   "display_name": "View All"
                   }
                   ]
  },
  {
  "name": "kids",
  "sub_category": [
                   {
                   "name": "earrings",
                   "display_name": "Earrings"
                   },
                   {
                   "name": "pendants",
                   "display_name": "Pendants"
                   },
                   {
                   "name": "view all",
                   "display_name": "View All"
                   }
                   ]
  }
  ]



DATA MODEL


//
//  Category.swift
//  Assignment
//
//  Created by vishnu duggisetty on 02/November/2019 .
//  Copyright © 2019 vishnu duggisetty. All rights reserved.
//

import Foundation

struct categoryDetails:Decodable {
    var name:String
    var sub_category:[subCategoryDetails]
}

struct subCategoryDetails:Decodable {
    var name:String
    var display_name:String
}





REQUEST

//
//  CategoryRequest.swift
//  Assignment
//
//  Created by vishnu duggisetty on 02/November/2019 .
//  Copyright © 2019 vishnu duggisetty. All rights reserved.
//

import Foundation

enum catError:Error{
    case NoData
    case ParseError
    case NoFilePath
}

struct CategoryRequest {
    func getCategories(completion:@escaping(Result<[categoryDetails], catError>) -> Void){
        if let filePath = Bundle.main.path(forResource: "category", ofType: "json"){
            do {
                 let jsonData = try Data(contentsOf: URL(fileURLWithPath: filePath), options: .mappedIfSafe)
                let decoder = JSONDecoder()
                let categoryResponse = try decoder.decode([categoryDetails].self, from: jsonData)
                print(categoryResponse)
                completion(.success(categoryResponse))
            } catch let error{
                print(error.localizedDescription)
                completion(.failure(.ParseError))
            }
        }
        else{
            completion(.failure(.NoFilePath))
        }
    }
}




//
//  CategoryTableViewController.swift
//  Assignment
//
//  Created by vishnu duggisetty on 02/November/2019 .
//  Copyright © 2019 vishnu duggisetty. All rights reserved.
//

import UIKit

class CategoryTableViewController: UITableViewController {

    var expandIndex: Int = -1
    var expandSectionHead: UITableViewHeaderFooterView!
    
    
    var listofCategories = [categoryDetails](){
        didSet{
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        self.getData()
        self.tableView.tableFooterView = UIView()
        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem
    }

    func getData(){
        let cateRequest = CategoryRequest()
        cateRequest.getCategories{[weak self] result in
            switch result{
            case .failure(let error):
                print(error)
            case .success(let categoryList):
                self?.listofCategories = categoryList
            }
        }
    }
    
    
    
    // MARK: - Table view data source

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        if(self.expandIndex == section){
            return self.listofCategories[section].sub_category.count
        }
        else{
            return 0
        }
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryTableCell", for: indexPath)
        let name = self.listofCategories[indexPath.section].sub_category[indexPath.row].name
        let displayName = self.listofCategories[indexPath.section].sub_category[indexPath.row].display_name
        cell.textLabel?.attributedText = makeAttributedString(title: name, subtitle: displayName)
        cell.textLabel?.numberOfLines = 0;
        return cell
    }
}

//TableView Sections
extension CategoryTableViewController{
    override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        if(self.listofCategories.count > 0){
            self.tableView.backgroundView = nil
            return self.listofCategories.count
        }
        
        return 0
    }
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        if(self.listofCategories.count > 0){
            return self.listofCategories[section].name.uppercased()
        }
        return ""
    }
    
    override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 60
    }
    
    override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
        let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView
        header.contentView.backgroundColor = UIColor.white
        header.textLabel?.textColor = UIColor.blue
        let separator = UIView(frame: CGRect(x: 0, y: header.frame.size.height, width: header.frame.size.width, height: 1))
        separator.backgroundColor = UIColor.lightGray
        header.addSubview(separator)

        header.tag = section
        let headerGesture = UITapGestureRecognizer()
        headerGesture.addTarget(self, action: #selector(self.sectionHeaderTap(_:)))
        header.addGestureRecognizer(headerGesture)
    }
}

//Expandable and collapsable methods
extension CategoryTableViewController{
    @objc func sectionHeaderTap(_ sender: UITapGestureRecognizer) {
        let headerView = sender.view as! UITableViewHeaderFooterView
        let section = headerView.tag
        //If no expanded cell, expandIndex will be -1
        if (self.expandIndex == -1) {
            self.expandIndex = section
            tableViewExpandSection(section)
        }
        else {
            //If same expand cell is selected, then collapse
            if (self.expandIndex == section) {
                tableViewCollapeSection(section)
            }
            else {
                //First collapse then expand
                tableViewCollapeSection(self.expandIndex)
                tableViewExpandSection(section)
            }
        }
    }
    
    func tableViewExpandSection(_ section: Int) {
        let subCat = self.listofCategories[section].sub_category
        if (subCat.count == 0) {
            self.expandIndex = -1;
            return;
        }
        else {
            var indexesPath = [IndexPath]()
            for i in 0 ..< subCat.count {
                let index = IndexPath(row: i, section: section)
                indexesPath.append(index)
            }
            self.expandIndex = section
            self.tableView!.beginUpdates()
                self.tableView!.insertRows(at: indexesPath, with: UITableView.RowAnimation.fade)
            self.tableView!.endUpdates()
        }
        
        //Scroll the tableview to selected cell
        self.tableView.scrollToRow(at: IndexPath(item: 0, section: section), at: .top, animated: true)
    }
    
    func tableViewCollapeSection(_ section: Int) {
        let subCat = self.listofCategories[section].sub_category
        //Make expandIndex to -1 when collapse is selected
        self.expandIndex = -1;
        if (subCat.count == 0) {
            return;
        }
        else {
            var indexesPath = [IndexPath]()
            for i in 0 ..< subCat.count {
                let index = IndexPath(row: i, section: section)
                indexesPath.append(index)
            }
            self.tableView!.beginUpdates()
                self.tableView!.deleteRows(at: indexesPath, with: UITableView.RowAnimation.fade)
            self.tableView!.endUpdates()
        }
    }
}

//Change the text format
extension CategoryTableViewController{
    func makeAttributedString(title: String, subtitle: String) -> NSAttributedString {
        let titleAttributes = [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline), NSAttributedString.Key.foregroundColor: UIColor.purple]
        let subtitleAttributes = [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .subheadline)]

        let titleString = NSMutableAttributedString(string: "\(title)\n", attributes: titleAttributes)
        let subtitleString = NSAttributedString(string: subtitle, attributes: subtitleAttributes)

        titleString.append(subtitleString)

        return titleString
    }
}







Monday, 26 August 2019

MVC,MVVM architecture in swift

In this article I am trying to show you some basics about MVC and MVVMarchitecture with example.
First let me start with MVC design pattern.
  1. Model (M)M stands for Model and in design architecture Model is used to just represent the data.
2. View (V)
V stands for View. View is used to display the data (received from Model or ViewModel). So basically XIB,Storyboard acts as a View.
3. Controller (C)
C stands for Controller. Controller is mediator between View and Model/ViewModel, it receives structured data form the Model/ViewModel and passes that data to View to display it on screen.
Now let’s see it by very simple example.
In this example,
  1. We are getting some data from our web server
  2. Then pass that data (received from web server) to our model class
  3. Then display that data (User’s list) using tableview
UserViewController.swift — Controller_image1
UserViewController.swift — Controller_image2
We are receiving some data from web server ( see Controller_image2) in Controller class and passes those data to our Model class ( see below Model_image3). And Model sends structured data back to controller.
UserInfo.swift- Model_image3
Then In tableview’s delegate method “cellForRowAt” we are passing particular userInfo object with its indexPath to display it to tableView’s cell. So here Controller is passing data to View to display it on screen.
UserInfoTbaleViewCell.swift — View_image4
UserInfoTableViewCell.swift class acts as a View in our project (see View_image4).
So we can see that in Controller class, we are receiving data from server and then pass that response to Model class and then Model class sends structured data to Controller class back. Then Controller class sends that structured data to View class to display it. So we can say that Controlleracts as mediator between Model and View class.
Another thing you might be noticed in MVC, View class is not just responsible to display data. it also performs some logic over the data and then display it. See below highlighted code implemented in UserInfoTbaleViewCell.swift — View_image4
var userInfo : UserInfo! {    didSet {           self.labelFname.text = userInfo.firstName ?? ""           self.labelLname.text = userInfo.lastName ?? ""           self.contentView.backgroundColor = (userInfo.isAdmin) ?   UIColor.red : UIColor.green          }}
you can see in above code, it first performs some logic (checking wether isAdmin property of userInfo is true or false ) for contentView’s background color and then accordingly it sets the background color.
So this is the main difference between MVC and MVVM, in MVVM “View class” is responsible for just displaying the data, it never performs any operation over data. To perform logic over data VM (View Model) class is there.

Now let’s see same example with MVVM Design pattern.

MVVM

In MVVM architecture, Model and Controller class are as same as in MVC.
While in MVVM one new class ViewModel is there, and there is a little change in View class.
Let’s see sequence of operations in MVVM
  1. Controller class passes data (received from server) to Model class.
  2. Model class returns structured data to Controller class.
  3. Controller class passes structured data to ViewModel class.
  4. ViewModel class performs logic over that data if needed and then sends back that final data to Controller class.
  5. Controller class passes final data to View class and finally View class displays that data on the screen.
UserVIewModel.swift — ViewModel_image5
UserViewController.swift — Controller_image6
UserViewController.swift — Controller_image7
UserInfoTableViewCell.swift — View_image8
So this is the difference between MVC and MVVM. The example which I have used is so simple, in real you can have complex project. But in this article I have tried to demonstrate the difference between MVC and MVVM in a very simple way.
Each pattern has some pros and cons. So it is up to you which pattern you need to use as per requirement.

Setting Up Multiple App Targets in Xcode from a Single Codebase

 To create two different apps (like "Light" and "Regular") from the same codebase in Xcode, you can follow these steps b...