4

I am working on a social ios application in swift similar to Instagram. I have 2 screens that contain almost the same display for feeds. The first one is a simple feeds screen that contains a tableview, the second one is the profile screen that contains a tableview header of profile info, and the tableview should contain same data of the first screen.

I was able to do that but I had to repeat the same code for tableview in both first and second screen: (cellforRow, Number, data, and calculations...)

what is the best approach to avoid duplicating data in such a case?

Kiran Sarvaiya
  • 1,318
  • 1
  • 13
  • 37
Jaafar Barek
  • 420
  • 3
  • 9

5 Answers5

7

You can achieve this by writing an separate tableview delegate and data source handler class which can handle the data displaying on behalf for view controller.

Handler:

import UIKit

class GenericDataSource: NSObject {

let identifier     = "CellId"
var array: [Any]           = []

func registerCells(forTableView tableView: UITableView) {
    tableView.register(UINib(nibName: "", bundle: nil), forCellReuseIdentifier: identifier)
  }

func loadCell(atIndexPath indexPath: IndexPath, forTableView tableView: UITableView) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
    return cell
  }
}

// UITableViewDataSource
extension GenericDataSource: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        return 0
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return array.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return self.loadCell(atIndexPath: indexPath, forTableView: tableView)
    }

}
// UITableViewDelegate
extension GenericDataSource: UITableViewDelegate {

        func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
            return UITableViewAutomaticDimension
        }

        func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
            return UITableViewAutomaticDimension
        }

        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)      {

        }
}
protocol GenericDataSourceDelegate: class {
            // Delegate callbacks methods
}

How to use it with view controller!

class MyViewControllerA: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    var dataSource = GenericDataSource()


    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.delegate = self.dataSource
        self.tableView.dataSource = self.dataSource
    }
}

class MyViewControllerB: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    var dataSource = GenericDataSource()


    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.delegate = self.dataSource
        self.tableView.dataSource = self.dataSource
    }
}
Venkat057
  • 187
  • 9
  • Is there any way of doing this same thing but passing the "array" and "tableView" variables from a ViewController to the GenericDataSource? I want to use the same UITableView prototype cells in multiple places in my app and was wondering if there is a way to not have to re write the "cellForRowAt" function – donny.rewq Jun 01 '18 at 03:39
  • Found the answer here: https://stackoverflow.com/questions/46898574/how-can-i-reuse-a-table-view-in-multiple-view-controllers-in-swift – donny.rewq Jun 01 '18 at 04:19
0

If two tableview contain the same data, you can extract a DataSource class to manage the data, put every repeat methods in this class, such as cellForRow, numberOfRow, cellForRow.

Then at your viewContoller, just init the DataSource class and set the datasource.

self.tableViewDataSource = DataSource() tableview.dataSource = self.tableViewDataSource

Same for UITableViewDelegate if two tableView have the same behavior.

johnny
  • 46
  • 5
0

I'd recommend implementing a UITableViewCell subclass where you can do the layout programatically or in a .xib. In the screens where you want to use those cells you call either self.tableView.registerClass(MYTableViewCell.self, forCellReuseIdentifier: "cell") if the layout is done programatically or self.tableView.registerNib(MYTableViewCell.self, forCellReuseIdentifier: "cell") if the layout is done in a .xib file.

Your class can have a configureWithData(data: SomeDataClass) function where you do all the calculations and populating the cell with the data you provide with the SomeDataClass instance.

PiKey
  • 259
  • 2
  • 6
0

If you need to same tableview in different view controller then Container Views is best option.

This link help for you :

Embedding TableView in TableViewController into another view

Swift table view Embedded in Container

Community
  • 1
  • 1
Mitesh jadav
  • 910
  • 1
  • 9
  • 26
-1
// EventStatus Types
enum EventStatusType: String {
    case ALL = "ALL"
    case LIVE_NOW = "LIVE_NOW"
    case HAPPENING_SOON = "HAPPENING_SOON"
    case COMING_UP = "COMING_UP"
    case ENDING_NOW = "ENDING_NOW"
}

class FilterTableHandler: NSObject, UITableViewDelegate,UITableViewDataSource {

    public typealias FilterTableCallback = ( Int, String ) -> Void
    private var callback: FilterTableCallback?
    var filterListArray = [FilterCellObject]()

    var table : UITableView!
    override init() {
        super.init()
    }

    func initializeFilterList() {
        filterListArray.append(FilterCellObject.init(title: "Live Now" , imageName:  "filterGreen", querystring: EventStatusType.LIVE_NOW.rawValue, selectionColor: UIColor(red: 0.973, green: 0.996, blue: 0.914, alpha: 1.00)))
        filterListArray.append(FilterCellObject.init(title: "Happening Soon" , imageName:  "filterYellow", querystring: EventStatusType.HAPPENING_SOON.rawValue, selectionColor: UIColor(red: 0.976, green: 0.925, blue: 0.902, alpha: 1.00)))
        filterListArray.append(FilterCellObject.init(title: "Coming Up" , imageName:  "filterOrange", querystring: EventStatusType.COMING_UP.rawValue, selectionColor: UIColor(red: 1.000, green: 0.945, blue: 0.918, alpha: 1.00)))
        filterListArray.append(FilterCellObject.init(title: "All" , imageName:  "", querystring: EventStatusType.ALL.rawValue, selectionColor: UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.00)))
    }

    init(tableView: UITableView,callback: @escaping FilterTableCallback) {
        super.init()
        initializeFilterList()
        self.callback = callback
        table = tableView

        tableView.allowsSelection = true
        tableView.separatorStyle = .none
        tableView.backgroundColor = UIColor.clear
        tableView.bounces = false
        tableView.register(UINib(nibName: "FilterTableViewCell", bundle: nil), forCellReuseIdentifier: "FilterTableViewCell")
        tableView.delegate = self
        tableView.dataSource = self
        table.delegate = self
        table.dataSource = self
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.filterListArray.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "FilterTableViewCell", for: indexPath) as! FilterTableViewCell

        cell.lblTitle.text = self.filterListArray[indexPath.row].title

        if self.filterListArray[indexPath.row].imageName.isNotEmpty {
            cell.colorImgView.image = UIImage(named:self.filterListArray[indexPath.row].imageName)
        }
        cell.customBackgroundView.backgroundColor = self.filterListArray[indexPath.row].backGroundSelectionColor

        return cell
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableViewAutomaticDimension
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        tableView.deselectRow(at: indexPath, animated: false)
        self.callback?(indexPath.row,self.filterListArray[indexPath.row].querystring)
    }

}

This class handles all table view delegate and dataSource. And returns data. Just design TableView and create IBOutlet of that table without adding cell in the storyboard. And create your tableview cell using xib to make it reusable.

Then, in your controller use FilterTableHandler like this

 var filerHandler = FilterTableHandler.init()
        self.filerHandler = FilterTableHandler.init(tableView: self.filterTableView, callback: { (index,queryString) in

//Your actions on cell selection
            self.hideFilterView()
            self.hideInfoView()
            self.getEventList(queryString: queryString)
            if index != 3 {
                //filter option selected
                self.btnFilter?.setImage(UIImage(named:"filterFilled"), for: .normal)
            }
            else {
                //"all" option selected
                self.btnFilter?.setImage(UIImage(named:"filter"), for: .normal)
            }
        })

Initialize FilterTableHandler object as shown, And receive selected data using custom callbacks. You can customize it as per your requirement. And tableview becomes reusable for whole app.

Pramod More
  • 1,220
  • 2
  • 22
  • 51