UITableview is visible while the cells aren't.
This is for a food ordering app, and I'm trying to display the menu. I've tried everything, no error has shown, but the cells ain't visible
import UIKit
import FirebaseDatabase
import FirebaseCore
class MenuVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
var menu = [Food]()
var ref: DatabaseReference?
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tableView.delegate = self
tableView.dataSource = self
ref = Database.database().reference()
loadMenu()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return menu.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "menuCell") as? MenuCell {
let foodItem = menu[indexPath.row]
cell.configCell(food: foodItem)
return cell
}else{
return UITableViewCell()
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "popup", sender: menu[indexPath.row])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let popupVC = segue.destination as? MenuPopUpVC {
if let foodItem = sender as? Food{
popupVC.config(food: foodItem)
}
}
}
func loadMenu() {
ref?.child("menu").observe(.value, with: { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let foodName = dict["name"] as! String
print(foodName)
let foodPrice = dict["price"] as! String
let foodImg = dict["image"] as! String
let foodItem = Food(name: foodName, price: foodPrice, img: foodImg)
self.menu.append(foodItem)
}
})
}
}
import UIKit
import SDWebImage
class MenuCell: UITableViewCell {
#IBOutlet weak var PriceLbl: UILabel!
#IBOutlet weak var menuImg: UIImageView!
#IBOutlet weak var menuItemLbl: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func configCell(food : Food) {
let name = food.name ?? ""
menuItemLbl.text = name
let price = food.price ?? ""
PriceLbl.text = "$\(price)"
menuImg.sd_setImage(with: URL(string: food.img!)) {[weak self] (image, error, cachetype, url) in
if error == nil{
self?.menuImg.image = image
}
}
}
}
You don't reload data of your TableView, reload them after you append all foods to menu array (means after foreach loop)
ref?.child("menu").observe(.value, with: { (snapshot) in
for child in snapshot.children {
...
self.menu.append(foodItem)
}
self.tableView.reloadData()
})
You need to reload the tableView after you fill the array
self.menu.append(foodItem)
}
self.tableView.reloadData()
Also inside cellForRowAt , it's a good practice to
let cell = tableView.dequeueReusableCell(withIdentifier: "menuCell") as! MenuCell
without the misleading return UITableViewCell()
Related
hello i am new to learning swift. I want to save data in database and show it in tableview. But even though I do everything, the tableview does not appear in the simulator. white screen appears. Any idea about the reason? I looked at the previous questions but couldn't find an answer.
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet weak var tableView: UITableView!
//tanımlamalar
var nameArray = [String]()
var idArray = [UUID]()
override func viewDidLoad() {
super.viewDidLoad()
//tableview gösterme
tableView.delegate = self
tableView.dataSource = self
//add butonu ekleme
navigationController?.navigationBar.topItem?.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.add, target: self, action: #selector(addButtonClicked))
}
//verileri getirme
func getData() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
//getirme
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName : "Paintings")
fetchRequest.returnsObjectsAsFaults = false
do{
let results = try context.fetch(fetchRequest)
for result in results as! [NSManagedObject] {
if let name = result.value(forKey: "name") as? String{
self.nameArray.append(name)
}
if let id = result.value(forKey: "id") as? UUID{
self.idArray.append(id)
}
//yeni veri eklenirse tableviewı reload et
self.tableView.reloadData()
}
}catch{
print("error")
}
}
//add butonuna tıklanırsa ne olacak
#objc func addButtonClicked() {
performSegue(withIdentifier: "DetailsVC", sender: nil)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return nameArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
var content = cell.defaultContentConfiguration()
content.text = nameArray[indexPath.row]
cell.contentConfiguration = content
return cell
}
}
I am a novice to swift. This is my first assignment for UI development. I have done the exercise perfectly and the tableView showed up as expected. The code is as below:
import UIKit
class ViewController: UIViewController {
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tableView.delegate = self
tableView.dataSource = self
}
}
extension ViewController: UITableViewDelegate{
}
extension ViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "gameScoreCell", for: indexPath)
cell.textLabel?.text = "hello world"
cell.detailTextLabel?.text = "score"
return cell
}
}
However, when I followed the same step and tried to integrate it with my project (with a navigation controller), the table view does not show up. Did I miss anything?
import UIKit
class HightScoreVC: UIViewController {
#IBOutlet var rankingTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
rankingTable.delegate = self
rankingTable.dataSource = self
}
}
extension HightScoreVC: UITableViewDelegate{
}
extension HightScoreVC: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "gameScoreCell", for: indexPath)
cell.textLabel?.text = "hello world"
cell.detailTextLabel?.text = "123"
return cell
}
}
I think you must register your cell in ViewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
rankingTable.delegate = self
rankingTable.dataSource = self
rankingTable.register(UITableViewCell.self, forCellReuseIdentifier: "gameScoreCell")
}
Points to keep in mind while working with tableview in swift.
Make sure constriants of tableview are given properly.
You have connected the class to the view controller in the identity inspector.
Provide delegate and datasource in viewDidLoad() itself rather than storyboard for better practice.
If you are creating xib for a cell, make sure you have registered the cell for your tableview, or if you are providing prototype cell, make sure you provide dequeueReusableCell() method and initialize your cell for some specific class.
Simple example for a tableview with some prototype cell
import UIKit
class UsersListViewController: UIViewController, Storyboarded {
//MARK: - Variables
var coordinator: AuthenticationCoordinator?
var usersList: UsersList?
//MARK: - Outlets
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var progressBar: UIActivityIndicatorView!
#IBOutlet weak var btnAddUser: UIButton!
//MARK: - UIViewController
override func viewDidLoad() {
super.viewDidLoad()
initializeView()
getUsersList()
}
//MARK: - Actions
#IBAction func addUserAction(_ sender: UIButton) {
coordinator?.presentAddUser()
}
//MARK: - File private functions
fileprivate func initializeView() {
self.title = "Users list"
progressBar.startAnimating()
btnAddUser.layer.masksToBounds = true
btnAddUser.layer.cornerRadius = btnAddUser.frame.height / 2
tableView.delegate = self
tableView.dataSource = self
}
fileprivate func getUsersList() {
guard let url = URL(string: ApiUrl.delayResponseURL.rawValue) else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else { return }
guard let data = data else { return }
guard let response = response as? HTTPURLResponse, (200 ..< 299) ~= response.statusCode else { return }
do {
guard let jsonObject = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { return }
guard let prettyJsonData = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted) else { return }
guard let responseData = try? JSONDecoder().decode(UsersList.self, from: prettyJsonData) else { return }
self.usersList = responseData
DispatchQueue.main.async {
self.progressBar.stopAnimating()
self.progressBar.alpha = 0
self.tableView.reloadData()
}
} catch {
return
}
}.resume()
}
}//End of class
//MARK: - UITableViewDelegate
extension UsersListViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let index = usersList?.data[indexPath.row].userID {
coordinator?.startSingleUserVC(index)
tableView.deselectRow(at: indexPath, animated: true)
}
}
}//End of extension
//MARK: - UITableViewDataSource
extension UsersListViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "singleUserCell") as? SingleUserTableViewCell {
if let url = URL(string: usersList?.data[indexPath.row].avatar ?? "") {
DispatchQueue.global().async {
guard let data = try? Data(contentsOf: url) else { return }
val currentUser = self.usersList?.data[indexPath.row]
DispatchQueue.main.async {
cell.initCell(data, currentUser.firstName, currentUser.email)
}
}
}
return cell
}
return UITableViewCell()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return usersList?.data.count ?? 1
}
}//End of extension
I'm coding a Note App in Swift 4. The root ViewController (NoteListViewController) gets populated when secondViewController (ComposeNoteViewController) Textfield and TextView are populated.
The problem is when I press a populated TableView cell, rather than fetch and display the content, it opens a fresh instance of theComposeNoteViewController.
import UIKit
import CoreData
class NoteListTableViewController: UITableViewController {
var noteListArray = [NoteListItem]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
loadNoteListItem()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return noteListArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "NoteListItemCell", for: indexPath)
cell.textLabel?.text = noteListArray[indexPath.row].title
return cell
}
//MARK: - TABLEVIEW DELEGATE METHODS
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "goToComposeNote", sender: self)
tableView.deselectRow(at: indexPath, animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! ComposeNoteViewController
if let indexPath = tableView.indexPathForSelectedRow {
destinationVC.selectedNoteList = noteListArray[indexPath.row]
}
}
import UIKit
import CoreData
class ComposeNoteViewController: UIViewController {
var noteComposeItemsArray = [ComposeNote]()
var noteListArray = [NoteListItem]()
// let noteListController = NoteListTableViewController()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var selectedNoteList : NoteListItem? {
didSet {
loadComposeItem()
}
}
#IBOutlet weak var noteTextView: UITextView!
#IBOutlet weak var noteTextField: UITextField!
#IBAction func noteSavePressed(_ sender: UIBarButtonItem) {
let newNoteTitleItem = NoteListItem(context: context)
let newComposeNote = ComposeNote(context: context)
newNoteTitleItem.title = noteTextField.text!
newComposeNote.note = noteTextView.text!
newComposeNote.parentTitleNote = selectedNoteList
noteComposeItemsArray.append(newComposeNote)
noteListArray.append(newNoteTitleItem)
saveComposeItems()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func saveComposeItems() {
do {
try context.save()
}catch {
print("Error saving context \(error)")
}
reloadInputViews()
}
func loadComposeItem() {
let request : NSFetchRequest<ComposeNote> = ComposeNote.fetchRequest()
let predicate = NSPredicate(format: "parentTitleNote.title MATCHES %#", selectedNoteList!.title!)
request.predicate = predicate
do {
noteComposeItemsArray = try context.fetch(request)
}catch {
print("Can't load Items")
}
reloadInputViews()
}
}
I have problem to get label from cell when i turn my switch ON. I do fetch all labels from Firebase Database.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tagCell", for: indexPath) as! TagsTableViewCell
print(myCallList[indexPath.row])
let _tag = myCallList[indexPath.row]
cell.tagLabel?.text = _tag.type
return cell
}
UPDATED:
UITableViewCell contain nothing special
import UIKit
class TagsTableViewCell: UITableViewCell {
#IBOutlet weak var tagLabel: UILabel!
#IBOutlet weak var tagSwitch: UISwitch!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
My model:
class Calls: NSObject {
var type: String?
init(type: String?) {
self.type = type
}
}
LoadCalls contain Firebase data fetch:
func LoadCalls() {
ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
self.myCallList.removeAll()
ref.child("tags").observe(.childAdded, with: { (snapshot) in
if snapshot != nil{
var tagType = snapshot.key as? String
let myCalls = Calls(type: tagType)
self.myCallList.append(myCalls)
print(self.myCallList.count)
DispatchQueue.main.async {
self.tagsTableView.reloadData()
}
}
})
}
A delegate / protocol for communication between cell and table controller can work well here.
protocol switchCellDelegate : Class {
func cellSwitchChanged( value: String, sender: Any)
}
update table view cell with property and IBAction for switch change
class TagsTableViewCell: UITableViewCell {
weak var delegate : switchCellDelegate?
#IBAction func switchChanged(sender: UISwitch){
guard let delegate = delegate else { return }
if sender.isOn {
delegate.cellSwitchChanged( value: tagLabel.text, sender: self)
}
}
and then in cellForRowAtIndex, add this
cell.delegate = self
and controller
extension myController : switchCellDelegate {
func cellSwitchChanged( value: String, sender: Any){
//do what you want here
}
}
I guess it something like this - add tag to switcher and create action for it
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tagCell", for: indexPath) as! TagsTableViewCell
print(myCallList[indexPath.row])
let _tag = myCallList[indexPath.row]
cell.tagLabel?.text = _tag.type
cell.switcher.tag = indexPath.row
return cell
}
And after this
#IBAction func switcherChanged(_ sender: UISwitch) {
var getLabel = myCallList[(sender as AnyObject).tag]
print(getLabel.type)
}
there are two attributes 'time' and 'isOn' (string, bool) in the entity named 'Item'
in viewcontroller class I am able to give default condition to 'isOn' attribute (in savePressed function) which makes switchbtn.isOn = true and saves it in the data model for that particular 'time'
viewcontroller class :-
class ViewController: UIViewController {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
#IBOutlet weak var timePickerView: UIDatePicker!
#IBOutlet weak var timeLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
timePickerView.setValue(UIColor.white, forKeyPath: "textColor")
dateFormat()
// Do any additional setup after loading the view.
}
#IBAction func savePressed(_ sender: UIBarButtonItem) {
let entity = Item(context: context)
entity.time = timeLbl.text
entity.isOn = true
saveData()
self.dismiss(animated: true, completion: nil)
}
#IBAction func cancelPressed(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
}
#IBAction func valueChanged(sender:UIDatePicker, forEvent event: UIEvent){
dateFormat()
}
func saveData() {
(UIApplication.shared.delegate as! AppDelegate).saveContext()
}
func dateFormat() {
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm"
formatter.timeStyle = .short
timeLbl.text = formatter.string(from: timePickerView.date)
}
}
viewcontroller
in this class I am able to fetch and show the core data but don't know how to save the state of the cell switch button and update the data model as there is no use of 'didSelectRowAt' function
tableview class :-
class TableViewController: UITableViewController {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var items = [Item]()
override func viewDidLoad() {
super.viewDidLoad()
print(arr)
}
override func viewWillAppear(_ animated: Bool) {
getData()
tableView.reloadData()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! TableViewCell
cell.timeLbl.text = items[indexPath.row].time
cell.switchBtn.isOn = items[indexPath.row].isOn
return cell
}
func getData() {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
items = try context.fetch(Item.fetchRequest())
}catch{
print("failed to get the data")
}
}
}
tableview
in this I am able to print the current state of the switch but cannot access the 'items[indexPath.row]' from the tableview class
cell class :-
class TableViewCell: UITableViewCell {
#IBOutlet weak var timeLbl: UILabel!
#IBOutlet weak var switchBtn: UISwitch!
var alarm = Bool()
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func valChange(_ sender: UISwitch) {
if sender.isOn{
switchBtn.isOn = true
}else {
switchBtn.isOn = false
}
}
}
In Swift the most efficient way is a callback closure.
In the cell add a property callback with a closure passing a Bool value and no return value. Call the callback when the value of the switch changed.
class TableViewCell: UITableViewCell {
#IBOutlet weak var timeLbl: UILabel!
#IBOutlet weak var switchBtn: UISwitch!
var alarm = Bool()
var callback : ((Bool) -> Void)?
#IBAction func valChange(_ sender: UISwitch) {
callback?(sender.isOn)
}
}
In cellForRow in the controller add the callback, in the closure update the model.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! TableViewCell
let item = items[indexPath.row]
cell.timeLbl.text = item.time
cell.switchBtn.isOn = item.isOn
cell.callback = { newValue in
self.items[indexPath.row].isOn = newValue
}
return cell
}
If cells can be inserted, deleted or moved you have to pass also the cell to get the actual index path
class TableViewCell: UITableViewCell {
#IBOutlet weak var timeLbl: UILabel!
#IBOutlet weak var switchBtn: UISwitch!
var alarm = Bool()
var callback : ((UITableViewCell, Bool) -> Void)?
#IBAction func valChange(_ sender: UISwitch) {
callback?(self, sender.isOn)
}
}
and
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! TableViewCell
let item = items[indexPath.row]
cell.timeLbl.text = item.time
cell.switchBtn.isOn = item.isOn
cell.callback = { currentCell, newValue in
let currentIndexPath = tableView.indexPath(for: currentCell)!
self.items[currentIndexPath.row].isOn = newValue
}
return cell
}