I have this certain class which handles the loading of a tableview from any viewcontroller. It is given as..
class TableViewConfig<ItemsType, CellType:UITableViewCell>: NSObject, UITableViewDataSource, UITableViewDelegate {
var emptyDataSet: Bool {
didSet {
if emptyDataSet {
tableView.tableFooterView = UIView()
}
}
}
var items: [ItemsType] {
didSet {
tableView.dataSource = self
tableView.delegate = self
tableView.reloadData()
}
}
// MARK: - Private Properties
typealias CellClosure = (_ item: ItemsType, _ cell: CellType) -> Void
// Tableview Config
private var tableView: UITableView
private var cellIdentifier: String
private var configureCellClosure: CellClosure
// Delegate
private var indexPathClosure: ((IndexPath?) -> Void)?
// MARK: - Public Methods
public func selectedRow(_ callBack: #escaping (IndexPath?) -> Void) {
indexPathClosure = callBack
}
// MARK: - Inialization
init(_ tableView: UITableView,
items: [ItemsType],
cellIdentifier identifier: String,
configClosure config:#escaping CellClosure) {
self.tableView = tableView
self.cellIdentifier = identifier
self.items = items
self.configureCellClosure = config
self.emptyDataSet = false
}
// MARK: UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath) as! CellType
cell.tag = indexPath.row
configureCellClosure(items[indexPath.row], cell)
return cell
}
private func item(at indexpath: IndexPath) -> ItemsType {
return items[indexpath.row]
}
// MARK: UITableViewDelegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let callback = indexPathClosure {
callback (indexPath)
}
}
}
This class will handle the loading of a tableview with data from any viewcontroller.
Now my problem is I want to use this class and show a tableview in my viewcontroller. How can I do that..? Hope someone can help...
you have to do something like yourViewController Tableview.delegate = TableViewConfig
Related
getting data from presenter in protocol not reflecting in tableview cell
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var presenter : Presenter?
var products = [Products]()
override func viewDidLoad() {
super.viewDidLoad()
presenter?.getDataFromInteractor()
}
func reloadTable(){
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
extension ViewController:UITableViewDataSource,UITableViewDelegate,PresenterProtocol{
func didFinishGettingDataFromPresenter(data: [Products]) {
print(data)
products = data
reloadTable()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return products.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell",for: indexPath) as! TableViewCell
cell.productName.text = products[indexPath.row].name
cell.productProducer.text = products[indexPath.row].producer
cell.productCost.text = "RS \(products[indexPath.row].cost)"
return cell
}
}
protocol PresenterProtocol {
func didFinishGettingDataFromPresenter(data:[Products])
}
class Presenter:InteractorProtocol {
var interactor : Interactor?
var presenter : PresenterProtocol?
// need data from interactor
func getDataFromInteractor() {
interactor?.fetch()
}
func didFinishGettingData(data: [Products]) {
presenter?.didFinishGettingDataFromPresenter(data: data)
}
}
I have a class, LocationViewController, which needs to implement a TableView. I have a function getParsedTestingLocation() which uses a completion handler from another function to get some data.
class LocationViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func getParsedTestingLocations(completion: #escaping ([TestingLocation]?, Error?) -> (Void)) {
getTestingLocationsJSON(completion: { testLocationsJSON, error in
if let testLocationsJSON = testLocationsJSON {
let testLocationsData = Data(testLocationsJSON.utf8)
let decoder = JSONDecoder()
do {
let testLocations = try decoder.decode([TestingLocation].self, from: testLocationsData)
completion(testLocations, nil)
} catch {
print(error)
}
}
})
}
}
I want to use the value testLocations within getParsedTestingLocations() in an external extension in this file. Here are the extensions I have:
extension LocationViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you tapped me!")
}
}
extension LocationViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "empty cell"
return cell
}
}
Within all 3 tableView() functions I want to get the values stored in testLocations in the completion handler in these functions. How could I do this?
Actually you don't need a completion handler. Reload the table view inside the completion closure of the API call
class LocationViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var locationData = [TestingLocation]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
getParsedTestingLocations()
}
func getParsedTestingLocations() {
getTestingLocationsJSON(completion: { testLocationsJSON, error in
if let testLocationsJSON = testLocationsJSON {
let testLocationsData = Data(testLocationsJSON.utf8)
let decoder = JSONDecoder()
do {
self.locationData = try decoder.decode([TestingLocation].self, from: testLocationsData)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}
})
}
}
extension LocationViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return locationData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let location = locationData[indexPath.row]
cell.textLabel?.text = location.whatEverStringYouWantToDisplay
return cell
}
}
Replace whatEverStringYouWantToDisplay with the real struct member name.
You don't need a completionHandler in getParsedTestingLocations in this case as the function already calls a function which has completionHandler. Just use a variable
class LocationViewController: UIViewController {
private lazy var locationArr = [TestingLocation]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
getParsedTestingLocations()
}
func getParsedTestingLocations() {
getTestingLocationsJSON(completion: { testLocationsJSON, error in
if let testLocationsJSON = testLocationsJSON {
let testLocationsData = Data(testLocationsJSON.utf8)
let decoder = JSONDecoder()
do {
let testLocations = try decoder.decode([TestingLocation].self, from: testLocationsData)
self.locationArr = testLocations
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error {
//Show Alert
}
}
})
}
}
extension LocationViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return locationArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = locationArr[indexPath.row].variable
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you tapped me! \(locationArr[indexPath.row])")
}
}
my cells are not appearing.
I did:
Checked if datasource and delegate were connected
Checked if my custom cells identifier name and class were correct
Things that I didn't:
I am struggling with auto layout, so I just decided not to do it.
My app is loading with the correct amount of cells, but the cells are not registered.
My code:
import UIKit
class WelcomeViewController: UITableViewController, NetworkManagerDelegate {
private var networkManager = NetworkManager()
private var infoForCells = [Result]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UINib(nibName: "ImageViewCell", bundle: nil), forCellReuseIdentifier: "imageCell")
networkManager.delegate = self
networkManager.fetchNews()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return infoForCells.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as? ImageViewCell else{
return UITableViewCell(style: .default, reuseIdentifier: "cell")
}
let cellIndex = infoForCells[indexPath.row]
cell.titleForImage.text = cellIndex.alt_description
print(cell.titleForImage ?? "lol")
// if let image = cellIndex.urlToImage {
// cell.imageForArticle.load(url: image)
// }
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func didUpdateNews(root: Root) {
infoForCells = root.results
}
}
Reload the table
func didUpdateNews(root: Root) {
infoForCells = root.results
tableView.reloadData()
}
In addition to Sh_Khan answer you can also listen to updates of infoForCells property
private var infoForCells = [Result]() {
didSet {
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
}
i have a tableview in a viewcontroller and because i need to reuse most of the code for another table i created an extra class:
class StatisticsViewDelegate: NSObject, UITableViewDelegate, UITableViewDataSource {
var defaultList:[String]
var infolist:[String] = []
var tableView:UITableView
var controller:UIViewController?
init(defaultList:[String], view:UITableView, controller:UIViewController?) {
self.defaultList = defaultList
self.controller = controller
tableView = view
super.init()
tableView.delegate = self
tableView.dataSource = self
loadTable()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return infolist.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "infocell", for: indexPath) as! TableViewCell
// [fill cell]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// [...]
}
func loadTable() {
DispatchQueue.global(qos: .userInitiated).async {
//[...]
// in this case:
self.infolist = self.defaultList
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
and in my UITViewController in the viewDidLoad():
delegate = StatisticsViewDelegate(defaultList: defaultList, view: tableView, controller:self)
delegate is a member of the ViewController
now when i run it, the function func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) never gets called. The func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) gets called however(before and after the reload) and returns the correct number(in my case 4). The TableView isn't visible at all. Where is my error?
Maybe you can use the subclassing strategy to resolve your problem. There are many reference passed to your class and if you forgot to clean that up you will be have memory leaks in your hand. So I'll suggest the simple example as below. You can modify as you like and let me know if that was what you are after. If not please pardon me.
//This will be parent class that will handle all table methods, so you need to write only once the delegates and stuffs
class MyCommonTableController: UITableViewController {
var infoList = [String]()
// MARK: - TableView Delegate and Datsource Impl
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return infoList.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 55.0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = infoList[indexPath.row]
return cell
}
}
The first class that is directly subclassing the from above MyCommonTableController
//In here we just have to know the data and set the infoList from parent
class TheTableViewController: MyCommonTableController {
let defaultList = ["Data1","Data2","Data3"] //....etc
override func viewDidLoad() {
super.viewDidLoad()
//this is were I will set those
infoList = defaultList
//reload the table
tableView.reloadData()
}
}
The second class that is directly subclassing the from above MyCommonTableController. Same process goes here
class TheSecondTableViewController: MyCommonTableController {
let defaultList = ["List1","List2","List3"] //....etc
override func viewDidLoad() {
super.viewDidLoad()
//this is were I will set those
infoList = defaultList
//reload the table
tableView.reloadData()
}
}
And now you are not repeating and table methods. You can also get the reference of table and use in your norma table view
#IBOutlet weak var theTable: UITableView!
let defaultList = ["List1","List2","List3"] //....etc
let commonTable = MyCommonTableController()
// MARK: - LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
commonTable.infoList = defaultList
commonTable.tableView = theTable
}
I have a master-detail view application in which MasterView's table cell is a Xib cell. I also have two TableViews in DetailViewController placed side by side as shown below:
Master and Detail Views
Now my problem is I am unable to populate DetailView's Table View when I am clicking on Master's TableViewCell. I am pasting my code below:
class MasterViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var devarlViewController: DetailViewController? = nil
var objects = [Any]()
var statesList = ["AL", "GA", "AK", "AR", "AZ", "CA", "CO", "CT", "CO"]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "cell")
// Do any additional setup after loading the view, typically from a nib.
}
// MARK: - Table View
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return statesList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel!.text = statesList[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
DetailViewController.sharedDetail.selectedState = (self.tableView(tableView, cellForRowAt: indexPath).textLabel?.text)!
DetailViewController.sharedDetail.selectedCity = ""
}
}
class DetailViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
static let sharedDetail = DetailViewController()
#IBOutlet weak var CitiesTable: UITableView!
#IBOutlet weak var PlacesTable: UITableView!
var selectedState: String = ""{
didSet{
CitiesTable.reloadData()
}
}
var selectedCity: String = ""
var citiesStates: [String:[String]] = [ "AL": ["Auburn", "Montgomery", "Birmingham"]
]
var placesCities: [String:[String]] = [ "Auburn": ["Dominos", "Pizzahut", "McDonalds"],
"Birmingham": ["iHop", "Coldstone", "WaffleHouse"]
]
var cities: [String] = []
var places: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(tableView == CitiesTable)
{
if(selectedState != "")
{
cities = citiesStates[selectedState]!
return cities.count
}
}
else
{
if(selectedCity != "")
{
places = placesCities[selectedCity]!
return places.count
}
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(tableView == CitiesTable)
{
let cell = tableView.dequeueReusableCell(withIdentifier: "citiesCell", for: indexPath)
cell.textLabel?.text = cities[indexPath.row]
return cell
}
else
{
let cell = tableView.dequeueReusableCell(withIdentifier: "placesCell", for: indexPath)
cell.textLabel?.text = cities[indexPath.row]
return cell
}
}
}
I have added a protocol in masterviewcontroller shown below
protocol MasterSelectionDelegate: class {
func itemSelected(_ item: AnyObject)
}
then, as splitviewcontroller sits as a rootviewcontroller.
In app delegate or during creation of Split View Controller. I have added below line
masterViewController.delegate = detailViewController
In didSelectRowAtIndexPath of master ViewController I have called it this:
`override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedItem = self.arrString[indexPath.row]
self.delegate?.itemSelected(selectedItem)
}`
Finally you have to implement it in the delegate in detail view controller
func itemSelected(_ item: AnyObject) {
itemSelected = item as? Array<String>
tableview.reloadData()
}