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])")
}
}
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 want to implement the different expand/collapse section in tableView and each sections contain the different kind of data.I'm getting the index of section when user click on button. Now i want when user click on button section collapse and expand.Initially first time all sections data should be hidden when user first time comes. See my code and screenshot.
ViewController Code:
enum ProfileItems: Int{
case namePicture = 0
case about = 1
}
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var items:[ProfileItems] = [.namePicture, .about]
var profile:[Profiles] = [
Profiles(name: "Ki", profileImage: "slide2"),
Profiles(name: "Kaleem", profileImage: "slide3"),
Profiles(name: "Jameel", profileImage: "slide2")
]
var about: [About] = [
About(title: "Johns"),
About(title: "Dons")
]
var isHiddenSection: Bool = false {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
registerCell()
}
func registerCell() {
tableView.delegate = self
tableView.dataSource = self
tableView.register(UINib(nibName: String(describing: PictureTvCell.self), bundle: .main), forCellReuseIdentifier: String(describing: PictureTvCell.self))
tableView.register(UINib(nibName: String(describing: AboutTVCell.self), bundle: .main), forCellReuseIdentifier: String(describing: AboutTVCell.self))
tableView.register(UINib(nibName: String(describing: HeaderTVCell.self), bundle: .main), forCellReuseIdentifier: String(describing: HeaderTVCell.self))
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: HeaderTVCell.self)) as? HeaderTVCell else {return UIView()}
switch items[section]{
case .namePicture:
cell.lableName.text = "Info"
case .about:
cell.lableName.text = "Collection"
}
cell.buttonCollpase.tag = items[section].rawValue
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch items[section] {
case .namePicture:
return profile.count
case .about:
return about.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch items[indexPath.section] {
case .namePicture:
return configurePictureCell(tableView, cellForRowAt: indexPath)
case .about:
return configureAboutCell(tableView, cellForRowAt: indexPath)
}
}
func configurePictureCell(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PictureTvCell.self)) as? PictureTvCell else {return UITableViewCell()}
cell.profiles = self.profile[indexPath.row]
return cell
}
func configureAboutCell(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: AboutTVCell.self)) as? AboutTVCell else {return UITableViewCell()}
cell.about = self.about[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40
}
}
extension ViewController: CollpaseSection {
func getIndex(_ index: Int) {
switch items[index] {
case .namePicture:
print("picture")
case .about:
print("about")
tableView.reloadData()
}
}
}
TableViewHederCell Code:
protocol CollpaseSection:AnyObject {
func getIndex(_ index: Int)
}
class HeaderTVCell: UITableViewCell {
#IBOutlet weak var lableName: UILabel!
#IBOutlet weak var buttonCollpase: UIButton!
weak var delegate: CollpaseSection?
#IBAction func didTapCollapse(_ sender: UIButton) {
delegate?.getIndex(sender.tag)
}
}
Screenshot current output:
enter image description here
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 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
I am using two view Controllers i.e. StateListVC and PlayVC. In StateListVC, I am using container view with 3 VCs. After tapping on 3rd View Controller, I am going to PlayVC with delegate method. I don't know how to do that. Please help me to resolve this issue.
StateListVC
class StateListVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
if isMoveToAnotherVC
{
let vc = self.storyboard?.instantiateViewController(withIdentifier: "PlaceVC") as! PlaceVC
vc.delegate = self
}
}
}
extension StateListVC: MoveToAnotherVC {
func moving(string: String) {
print(string)
}
}
ThirdVC
protocol MoveToAnotherVC {
func moving(string: String)
}
class PlaceVC: UIViewController {
#IBOutlet weak var tableViewPlace: UITableView!
var delegate: MoveToAnotherVC? = nil
var arrPlace = ["Place1", "Place2"]
override func viewDidLoad() {
super.viewDidLoad()
}
extension PlaceVC: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrPlace.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = arrPlace[indexPath.row]
return cell
}
}
extension PlaceVC: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
isMoveToAnotherVC = true
print(delegate)
guard let delegate = self.delegate else{
print("Delegate not set")
return
}
delegate.moving(string: "test")
}
}
This is hooked up fine:
protocol MoveToAnotherVC: AnyObject {
func moving(string: String)
}
class StateListVC: UIViewController, MoveToAnotherVC {
override func viewDidLoad() {
super.viewDidLoad()
let vc = self.storyboard?.instantiateViewController(withIdentifier: "PlaceVC") as! PlaceVC
vc.delegate = self
}
func moving(string: String) {
print(string)
}
}
class PlaceVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableViewPlace: UITableView!
weak var delegate: MoveToAnotherVC?
var arrPlace = ["Place1", "Place2"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrPlace.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = arrPlace[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.moving(string: "test")
}
}
I made the protocol a class protocol, moved the view controller instantiation into viewDidLoad, removed some (what I consider) extraneous unwrapping, and stripped your protocol/delegate pattern down to the basics. This works. I would plug this into your project and add what you need piece by piece until it breaks because your problem is outside of this. I suspect it may have to do with something you didn't include in your question but this answers your question about how to set up a protocol/delegate pattern.