iOS Swift Dequeued Reusable Cell not showing up - ios

I followed a tutorial earlier to get the basics of the Storyboard down and I'm using that code as a reference to write the app I'm working on. I want to test my prototype cell layout, but even though I set a value to the array at viewDidLoad it still refuses to show anything.
import UIKit
class DetailViewController: UIViewController {
var cards = [Card]()
#IBOutlet weak var cardsTableView: UITableView!
#IBOutlet weak var detailDescriptionLabel: UILabel!
var detailItem: AnyObject? {
didSet {
// Update the view.
self.configureView()
}
}
func configureView() {
// Update the user interface for the detail item.
if let detail: AnyObject = self.detailItem {
if let label = self.detailDescriptionLabel {
label.text = detail.description
}
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cards.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//példányosítunk egy cellát
let cell = tableView.dequeueReusableCellWithIdentifier("CardCell", forIndexPath: indexPath) as CardCell
//kivesszük a sor adatait a listából
let card : Card = self.cards[indexPath.row]
cell.setCardNumber(card.number)
cell.layer.cornerRadius = 8
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
self.cards = [Card(number: 123456789,type: 1)]
dispatch_async(dispatch_get_main_queue(), {
self.cardsTableView.reloadData()
})
self.configureView()
}
}
I started from a Master-Detail structure and I set the class of the Detail Scene to DetailViewController and the class and identifier of the Prototype to CardCell
class Card{
let number: Int
let type: Int
init(number: Int, type: Int){
self.number = number
self.type = type
}
}
import UIKit
class CardCell: UITableViewCell {
#IBOutlet weak var cardNumber: UILabel!
func setCardNumber(number: Int){
cardNumber.text = String(number)
}
}
I'm sure it's something basic, but I've been messing with this for the second day now. Any suggestions are appreciated.

you have to set the dataSource for the table view, i guess the delegate methods get never called.
Additionally add UITableViewDataSource, not necessary but more declarative than just the implementation
class DetailViewController: UIViewController, UITableViewDataSource {

Related

Xib file UITableViewCell outlets are nil

This is a bit of an ongoing topic, but my situation is slightly different. I'm working with this tutorial. I have a view controller, that has it's own storyboard, this view controller has table view. The view controller is this table's delegate and data source. I need to add different kinds of cells to this table, and I'm using cell view model as well for this.
Cell:
class TitleTextCell: UITableViewCell, CellConfigurable {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var textView: UITextView!
static let identifier = "TitleTextCell"
func setup(viewModel: RowViewModel) {
guard let titleTextViewModel = viewModel as? TitleTextViewModel else {return}
titleLabel.text = titleTextViewModel.title //fatal: found nil while
textView.text = titleTextViewModel.text //unwrapping an optional value
}
}
Table view controller's ViewController:
class InfoViewController: UIViewController, Storyboarded {
// MARK: - Properties
var viewModel: InfoViewModelType! {
didSet {
viewModel.viewDelegate = self
}
}
// MARK: - Outlets
#IBOutlet weak var tableView: UITableView!
// MARK: - Lifecycle Methods
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(TitleTextCell.self, forCellReuseIdentifier: TitleTextCell.identifier)
tableView.delegate = self
tableView.dataSource = self
}
}
extension InfoViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.tableRows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let rowViewModel = viewModel.tableRows[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier(for: rowViewModel), for: indexPath)
if let cell = cell as? CellConfigurable {
cell.setup(viewModel: rowViewModel)
}
return cell
}
private func cellIdentifier(for viewModel: RowViewModel) -> String {
switch viewModel {
case is TitleTextViewModel:
return TitleTextCell.identifier
default:
fatalError("Unexpected view model type: \(viewModel)")
}
}
}
The cell is a xib file. Outlets are connected with the file owner (see screens).
It does arrive at the point of func setup(viewModel: RowViewModel) which means table-wise it's correct. But the outlets are nil at runtime, what am I missing?
You need
tableView.register(UINib(nibName: "TitleTextCell", bundle: nil), forCellReuseIdentifier: TitleTextCell.identifier)
For xib cells

ios swift tableview not showing custom cells

I am trying to create a table view with custom cells from Storyboard layout in an iOS app.
But for some reason the table cells are not being shown. When I tried to set debug breakpoints I found that the debugger is reaching this function
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
but it never reaches this function -
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
Here is my viewcontroller code -
extension NavigationViewController: UITableViewDataSource, UITableViewDelegate, SideMenuControllerDelegate {
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SideMenuTableItem", for: indexPath as IndexPath) as! SideMenuTableItem
cell.setItemData(items[indexPath.row])
return cell
}
public func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func setupTableViews() {
menuTable.register(SideMenuTableItem.self, forCellReuseIdentifier: "SideMenuTableItem")
}
}
class SideMenuTableItem: UITableViewCell {
#IBOutlet weak var menuImage: UIImageView!
#IBOutlet weak var menuLabel: UILabel!
var data: MenuItem?
override func awakeFromNib() {
super.awakeFromNib()
}
func setItemData(_ item: MenuItem) {
data = item
menuLabel.text = data?.title
if data?.icon_res != nil {
menuImage.image = UIImage(named: (data?.icon_res)!)
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
I have checked in the storyboard that I have set the reusable identifier to the table prototype cell and also connected the datasource and the delegate properties to the tableview
and I am calling the setupTableViews() method inside my viewDidLoad() function after creating the items array
But still I am not able to get the cells to appear in my view at all.
Can anyone suggest what am I missing here or what's wrong with my code, or how can I further debug this issue
import UIKit
import SideMenuSwift
class NavigationViewController: UIViewController {
#IBOutlet weak var navigationContainer: UIView!
#IBOutlet weak var emailButton: UIButton!
#IBOutlet weak var phoneButton: UIButton!
#IBOutlet weak var userAvatar: UIImageView!
#IBOutlet weak var userProfile: UIButton!
#IBOutlet weak var userName: UILabel!
#IBOutlet weak var menuTable: UITableView!
var service: AuthenticationService!
var cdc: CoreDataController!
var items: [MenuItem] = []
var currentUser: User?
override func viewDidLoad() {
super.viewDidLoad()
setupSidebar()
initSidebarData()
setupUserHeader()
setupTableViews()
}
func setupUserHeader() {
if currentUser != nil {
if currentUser?.name != nil {
userName.text = currentUser?.name
} else if currentUser?.role != nil {
userName.text = "urTutors " + (currentUser?.role ?? "")
}
if currentUser?.avatarUrl != nil {
userAvatar.downloaded(from: (currentUser?.avatarUrl)!)
}
}
}
func initSidebarData() {
service = AuthenticationServiceProvider()
cdc = CoreDataController()
items = cdc.getNavigationData()
currentUser = cdc.getUserData()
}
func setupSidebar() {
self.view.backgroundColor = UIColor.hexColor("#fff")
navigationContainer.backgroundColor = UIColor.hexColor("#2a2a2a")
SideMenuController.preferences.basic.statusBarBehavior = .hideOnMenu
SideMenuController.preferences.basic.position = .above
SideMenuController.preferences.basic.direction = .left
SideMenuController.preferences.basic.enablePanGesture = true
SideMenuController.preferences.basic.menuWidth = 275
sideMenuController?.delegate = self
}
static func createViewController() -> NavigationViewController {
let sb = UIStoryboard(name: "StudentHomeModuleStoryboard", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "NavigationViewController")
return vc as! NavigationViewController
}
}
--UPDATE--
updated setupTableLayout function -
func setupTableViews() {
let bundle = Bundle(for: type(of: self))
let cellNib = UINib(nibName: "SideMenuTableItem", bundle: bundle)
menuTable.register(cellNib, forCellReuseIdentifier: "SideMenuTableItem")
menuTable.register(SideMenuTableItem.self, forCellReuseIdentifier: "SideMenuTableItem")
menuTable.reloadData()
}
After breaking into chat on this, we found that there were two issues.
The first issue was the missing reloadData call mentioned above. That was causing cellForRow to not be called. Adding reloadData corrected that issue, but then the custom cell class's outlets were nil, causing a crash in setItemData.
The second issue was that register(_:forCellReuseIdentifier:) was being called in code, but the custom cell was already setup as part of the Interface Builder UITableView declaration. Calling register again on the custom class re-registered the reuseIdentifier, disconnecting the outlets set up in the storyboard.
Removing the register call and adding reloadData solved all issues.
You are never calling setupTableViews(). You'r code should look like this:
class NavigationViewController: UIViewController, SideMenuControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
setupTableViews()
}
func setupTableViews() {
menuTable.reloadData()
}
}
extension NavigationViewController: UITableViewDataSource, UITableViewDelegate {
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SideMenuTableItem", for: indexPath as IndexPath) as! SideMenuTableItem
cell.setItemData(items[indexPath.row])
return cell
}
public func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
}
You are never calling the function, nor calling viewDidLoad. This should help. Also, where is the rest of your view controller code (is this all of it? It should not be!).
You don't need to register your cell because you requested it and make sure you reloadData().
Hope this helps!

How to use KVO to update tableViewCells based on underlying array element changes?

I have a table view representing an underlying array. The cells have a label and a slider which should show the value of the percentage property of the array.
I want to use key-value observing to update the label whenever the percentage property changes. (I know KVO is overkill in this example but eventually sliding one slider will affect the other cells including the position of the slider and the underlying array will be set from multiple places in the app and at any time so KVO is the way to go.)
I've had a bunch of help from this answer, but I can't get it to fire and update the label. I'm including all my code here. Not sure where I'm going wrong.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, CustomCellDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
for i in 0...4 {
items.append(Items(ID: i, percentage: 50))
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: myTableViewCell.ID) as? myTableViewCell {
cell.object = items[indexPath.row]
cell.mySlider.tag = indexPath.row
return cell
} else {
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
#IBAction func sliderValueChanged(_ sender: UISlider) {
items[sender.tag].percentage = Double(sender.value)
print("percentage at \(items[sender.tag].ID) is \(items[sender.tag].percentage)")
}
func didUpdateObject(for cell: UITableViewCell) {
if let indexPath = tableView.indexPath(for: cell) {
tableView.reloadRows(at: [indexPath], with: .automatic)
print("hello")
}
}
}
class myTableViewCell: UITableViewCell {
static let ID = "myCell"
weak var delegate: CustomCellDelegate?
private var token: NSKeyValueObservation?
var object: Items? {
willSet {
token?.invalidate()
}
didSet {
myLabel.text = "\(object?.percentage ?? 0)"
token = object?.observe(\.percentage) { [weak self] object, change in
if let cell = self {
cell.delegate?.didUpdateObject(for: cell)
}
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var mySlider: UISlider!
}
class Items: NSObject {
let ID: Int
#objc dynamic var percentage: Double
init(ID: Int, percentage: Double){
self.ID = ID
self.percentage = percentage
super.init()
}
}
var items: [Items] = []
protocol CustomCellDelegate: class {
func didUpdateObject(for cell: UITableViewCell)
}
To do the KVO in Swift 4, you have to declare the property as dynamic and call observe(_:options:changeHandler:) on that object, saving the resulting NSKeyValueObservation token. When that token falls out of scope (or replaced with another token), the original observer will automatically be removed.
In your case, you have your observer calling the delegate, which then reloads the cell. But you never appear to set that delegate property, so I suspect that method isn't getting called.
But this all seems a bit fragile. I'd be inclined to just update the label directly in the observer's changeHandler. I also think you can do a more direct updating of the cell (I'd put the "value changed" IBAction in the cell, not the table view), and eliminate that rather awkward use of the tag to identify which row in the model array had its slider updated (which can be problematic if you insert or delete rows).
So consider this object:
class CustomObject: NSObject {
let name: String
#objc dynamic var value: Float // this is the property that the custom cell will observe
init(name: String, value: Float) {
self.name = name
self.value = value
super.init()
}
}
You could then have a table view controller that populates an array of objects with instances of this model type. The details here are largely unrelated to the observation (which we'll cover below), but I include this just to provide a complete example:
class ViewController: UITableViewController {
var objects: [CustomObject]!
override func viewDidLoad() {
super.viewDidLoad()
// self sizing cells
tableView.estimatedRowHeight = 60
tableView.rowHeight = UITableViewAutomaticDimension
// populate model with random data
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
objects = (0 ..< 1000).map {
CustomObject(name: formatter.string(for: $0)!, value: 0.5)
}
}
}
// MARK: - UITableViewDataSource
extension ViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.object = objects[indexPath.row]
return cell
}
}
Having done that, you can now have the base class for your cell (a) update the model object if the slider changes; and (b) observe changes to that dynamic property, in this example updating the label when the value changes are observed in the model object:
class CustomCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var valueLabel: UILabel!
#IBOutlet weak var valueSlider: UISlider!
static private let formatter: NumberFormatter = {
let _formatter = NumberFormatter()
_formatter.maximumFractionDigits = 2
_formatter.minimumFractionDigits = 2
_formatter.minimumIntegerDigits = 1
return _formatter
}()
private var token: NSKeyValueObservation?
weak var object: CustomObject? {
didSet {
let value = object?.value ?? 0
nameLabel.text = object?.name
valueLabel.text = CustomCell.formatter.string(for: value)
valueSlider.value = value
token = object?.observe(\.value) { [weak self] object, change in
self?.valueLabel.text = CustomCell.formatter.string(for: object.value)
}
}
}
#IBAction func didChangeSlider(_ slider: UISlider) {
object?.value = slider.value
}
}
That yields:
For more information, see the "Key-Value Observing" section of the Using Swift with Cocoa and Objective-C: Adopting Cocoa Patterns.
hi #sean problem is in UITableview cell class you have already make diSet Method , so you dont need to pass value for cell.lable and slider Just try below code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: myTableViewCell.ID) as? myTableViewCell {
//pass the object to which you wanna add observer to cell
cell.object = items[indexPath.row]
return cell
} else {
return UITableViewCell()
}
}

Change Data with Switch on UITableViewCell

i am quite new to iOS and Swift and want to solve a Problem with UITableViewCell
I have a ControllerClass with a UITableView that has a Custom UITableViewCell called ArtistCell as following
public class ArtistCell: UITableViewCell {
var value : Bool = false
#IBOutlet weak var artistSwitch: UISwitch!
#IBOutlet weak var artistTextField: UITextField!
#IBAction func changedBoolValue(sender: UISwitch) {
self.value = sender.on
}
public func configure(text: String, enabledArtist: Bool) -> Bool{
self.artistSwitch.on = enabledArtist
self.value = enabledArtist
self.artistTextField.text = text
return self.value
}
In this class as you can see, there is a textfield and a switch. If this switch is clicked the value of this list item in my ViewController should be changed
import UIKit
class ProfileViewController: UIViewController, WCSessionDelegate, UITableViewDataSource, UITableViewDelegate {
...
#IBOutlet weak var tableView: UITableView!
var profile : UserProfile = UserProfile()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 10
...
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.profile.artists.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//get my own cell
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! ArtistCell
//initialize cell and get back the actual Value of the switch and set it to my object???
let boolValue : Bool = cell.configure( self.profile.artists[indexPath.row].name, enabledArtist: self.profile.artists[indexPath.row].display.boolValue )
//this following value should be set in my object
profile.artists[indexPath.row].display = boolValue
return cell
}
}
now i want to know how i should set the bool value of my switch to my object?
Define protocol before ArtistCell:
protocol ArtistTableViewCellDelegate {
func didChangeSwitchValue(value: Bool, artistName: String)
}
public class ArtistCell: UITableViewCell {
var delegate: ArtistTableViewCellDelegate?
var value: Bool = false
#IBOutlet weak var artistSwitch: UISwitch!
#IBOutlet weak var artistTextField: UITextField!
#IBAction func changedBoolValue(sender: UISwitch) {
self.value = sender.on
delegate?.didChangeSwitchValue(value, artistName: artistTextField.text!)
}
}
And in your ViewController:
class ProfileViewController: UIViewController, WCSessionDelegate, UITableViewDataSource, UITableViewDelegate, ArtistTableViewCellDelegate {
//...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//get my own cell
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! ArtistCell
//initialize cell and get back the actual Value of the switch and set it to my object???
let boolValue : Bool = cell.configure( self.profile.artists[indexPath.row].name, enabledArtist: self.profile.artists[indexPath.row].display.boolValue )
//this following value should be set in my object
profile.artists[indexPath.row].display = boolValue
cell.delegate = self
return cell
}
//...
func didChangeSwitchValue(value: Bool, artistName: String) {
//do sth with your value
}
}
You can also do some refactor in ArtistCell to achieve:
func didChangeSwitchValue(value: Bool, artistID: Int)
or:
func didChangeSwitchValue(value: Bool, artist: YOUR_ARTIST_TYPE)
Now you have default value from switch when your cell is created. To set new value for dataModel in ViewController when your switch state changed you can use delegate mechanism.
Create protocol for your action:
protocol SwitchChangedDelegate {
func changeStateTo(isOn: Bool, row: Int)
}
Make your ProfileViewController class confirm this protocol:
class ProfileViewController: UIViewController, WCSessionDelegate, UITableViewDataSource, UITableViewDelegate, SwitchChangedDelegate {
...
func changeStateTo(isOn: Bool, row: Int) {
// here update your dataModel
profile.artists[row].display = isOn
}
...
}
Add to your ArtistCell delegate object with type on protocol and row variable:
var delegate: SwitchChangedDelegate?
var row: Int?
Set delegate and row at your func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath):
cell.delegate = self
cell.row = indexPath.row
Call protocol method in your changedBoolValue func:
#IBAction func changedBoolValue(sender: UISwitch) {
...
self.delegate?.changeStateTo(sender.on, row: row)
}

Swift label not displaying what the selected cell says

I have a tableview that is populated with information from a JSON array. I want to make each selected cell segue into a viewController, and in that viewController I have a label the should display what the selected cell says. For example if my cell says California, when I click on the cell it'll open up my viewController and the label would say California.
Seems simple enough, and I've done this before successfully, however this time I'm using JSON to populate my tableView and I'm guessing I'm doing something wrong. With the code posted below, when I click on a cell the titleLabel doesn't even show up.
(My tableView file and DetailsViewController file are posted below, any other swift file I used can be found in my previous question populating Tableview with a function that uses SwiftyJSON)
import UIKit
class EarthTableViewController: UITableViewController {
var info = [AppModel]()
func getEarthquakeInfo(completion: (results : NSArray?) ->Void ){
DataManager.getEarthquakeDataFromFileWithSuccess {
(data) -> Void in
let json = JSON(data: data)
if let JsonArray = json.array {
for appDict in JsonArray {
var ids: String? = appDict["id"].stringValue
var title: String? = appDict["title"].stringValue
var time: String? = appDict["time"].stringValue
var information = AppModel(idEarth: ids, title: title, time: time)
self.info.append(information)
completion(results: self.info)
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
getEarthquakeInfo { (info) in
self.tableView.reloadData()
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as UITableViewCell
let infoArray = self.info
cell.textLabel!.text = self.info[indexPath.row].title
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "SEGUE" {
let vc = segue.destinationViewController as DetailsViewController
let cell = (sender as UITableViewCell)
let title = cell.textLabel!.text
vc.titleData = title
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return info.count
}
}
My DetailsViewController file:
import UIKit
class DetailsViewController: UIViewController {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var idLabel: UILabel!
#IBOutlet weak var timeLabel: UILabel!
var titleData: String!
var idData: String!
var timeData: String!
override func viewDidLoad() {
super.viewDidLoad()
var earthInfo = EarthTableViewController()
var getEarthInfo: () = earthInfo.getEarthquakeInfo { (info) in
println("\(info)")
}
titleLabel.text = titleData
idLabel.text = idData
timeLabel.text = timeData
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}

Resources