Crash at tableview.reloaddata with error Unexpectedly found nil - ios

On the click of a create button, I navigate to tableviewcontroller screen. But nothing is populated in the tableview since the array which populates the tableview hasn't been called yet.
Now once the tableview screen is reached, after a few seconds another method is called elsewhere(in another file), which in turn calls a function in this tableviewcontroller screen. This is that method of the tableviewcontroller screen which is called...
func stopIndicator(thegrpName: String) {
stopIndicator()
let realm = try! Realm()
let chatGrp = realm.objects(ChatGroup.self)
chatGroup = chatGrp
tableview.reloadData() //CRASH HAPPENS HERE
}
In this method, once I reach tableview.reloadData() it crashes with the error Unexpectedly found nil while unwrapping an optional value..
I referred this link which seems to have a similar problem...but couldn't get much help out of it...
What could be the reason for this crash...?
EDIT 1: The numberOfRows and cellForRowAt.. is given like so...
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let chatGrp = chatGroup {
return chatGrp.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: ChatGroupTableViewCell = tableView.dequeueReusableCell(withIdentifier: "chatgroupIdentifier") as! ChatGroupTableViewCell
let groupChatObj = chatGroup![indexPath.row]
cell.chatLabel.text = groupChatObj.lastMessage?.text?.text
return cell
}

It looks you are trying to create delegate method but in another file where you are trying to call delegate method stopIndicator you are calling method on singleton instead which gives you an error.
So, set delegate right. First create protocol
protocol YourProtocol {
func stopIndicator(thegrpName: String)
}
then in another file create delegate property
var delegate: YourProtocol?
now when you need to call delegate method stopIndicator call this
delegate?.stopIndicator(thegrpName: ...)
now add to your ViewController protocol
ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, YourProtocol
and now somewhere set your another file class delegate as your ViewController (if its view set it in viewDidLoad if it is another ViewController set it in prepareForSegue)
fileClass.delegate = self

Related

UITableView.reloadData() is not refreshing tableView. No error message

I've already looked at the post UITableView.reloadData() is not working. I'm not sure that it applies to my situation, but let me know if I'm wrong.
My app has a tableView. From the main viewController I am opening another viewController, creating a new object, and then passing that object back to the original viewController, where it is added to an array called timers. All of that is working fine. However, when I call tableView.reloadData() in didUnwindFromNewTimerVC() to display the updated contents of the timers array, nothing happens.
NOTE: I have verified that the timers array is updated with the new object. Its count increments, and I can access its members. Everything else in didUnwindFromNewTimerVC() executes normally. The tableView just isn't updating to reflect it.
Here is my code:
import UIKit
class TimerListScreen: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tabelView: UITableView!
var timers = [Timer]()
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
tabelView.delegate = self
tabelView.dataSource = self
let tempTimer = Timer(timerLabel: "temp timer")
timers.append(tempTimer)
}
#IBAction func didUnwindFromNewTimerVC(_sender:UIStoryboardSegue){
guard let newTimerVC = _sender.source as? newTimerVC else{return}
newTimerVC.timer.setTimerLabel(timerLabel: newTimerVC.timerLabel.text!)
timers.append(newTimerVC.timer)
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tabelView.dequeueReusableCell(withIdentifier: "TimerCell", for: indexPath) as? TimerCell{
let timer = timers[indexPath.row]
cell.updateUI(Timer: timer)
return cell
}else{
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return timers.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 78
}
}
Thank you
Please note the spelling. There are two table view instances: the outlet tabelView and a (pointless) instance tableView.
Reload the data of the outlet
tabelView.reloadData()
and delete the declaration line of the second instance let tableView ....
However I'd recommend to rename the outlet to correctly spelled tableView (you might need to reconnect the outlet in Interface Builder).
And force unwrap the cell
let cell = tabelView.dequeueReusableCell(withIdentifier: "TimerCell", for: indexPath) as! TimerCell
and remove the if - else part. The code must not crash if everything is hooked up correctly in IB.

Passing message from View Controller to a class in Swift

By using delegates, I'm trying to pass data from a view controller to a TableView class that will display the correct number of cells. First, I needed to pass the array to that class (using a String here instead for debugging)
Inside my ViewController:
protocol GroupBillVCDelegate{
func passFriendArray(string: String)
}
...
class GroupBillViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
var delegate:GroupBillVCDelegate? // for delegate passing message
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (tableView == self.FriendTableView){
let friendListCell = tableView.dequeueReusableCell(withIdentifier: "friendListCell") as! CategoryRow
// transfer friends array to CategoryRow
self.delegate?.passFriendArray(string: "Hello There")
return friendListCell
}
}
}
Inside the TableViewCell class:
class CategoryRow: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, GroupBillVCDelegate {
var s:String = String()
func passFriendArray(string: String) {
s = string
print(string)
}
...
// used for horizontal scrolling
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
passFriendArray(string: s) // message supposed to appear here
print ("TEST")
return 10
}
...
}
I have trouble setting up the delegate. When I run, the console does not display the message. Why is it not being passed?
Think of a delegate as an intercom on your desk that can be connected to your assistant's office.
This line:
var delegate:GroupBillVCDelegate? // for delegate passing message
Defines the intercom, but doesn't connect it to anything. If you press the button on the intercom, it doesn't connect to anything.
The code self.delegate?.passFriendArray(string: "Hello There")
uses "optional chaining" to send a message to the delegate if there is one, and drop the message if there is no delegate.
The thing you're missing is assigning something to the delegate:
self.delegate = someObjectThatConformsToGroupBillVCDelegateProtocol
A delegate is a one-to-one relationship.
All that being said, it doesn't really make sense to make a table view cell the delegate of a view controller. The table view has more than one cell. Which cell should be the view controller's delegate? Why that cell and not another one?

tableView reloadData off of delegate

Have a right view controller that slides in and out over the main view controller. This right view controller has a table in it to contain the passed information from the main.
I can access and pass the data to the controller from the main without issue but in the right view I need to then bind the data passed to it from the main.
The problem is that even though I try binding the data after the view comes into focus it gives nil on the tableView.reloadData().
RightViewController has 2 functions that are used by the main
func loadAlerts(){
self.tableView.reloadData()
}
func setAlerts(alerts: Alerts){
self.alerts = alerts
}
Alerts is just a custom object. It does contain values. self.alerts is a class variable.
MainViewController calls these 2 functions this way
self.rightViewController = self.storyboard?.instantiateViewController(withIdentifier: "RightViewController") as! RightViewController
Set the data after getting it from the api call
if let count = self.alerts?.Alerts.count {
if count == 0 {
return
}
//set on controller
rightViewController.setAlerts(alerts: self.alerts!)
}
This is defined at class level like
private var rightViewController: RightViewController!
Then I have a delegate defined for when the right controller is opened from a gesture and it calls like this
func rightDidOpen() {
rightViewController.loadAlerts()
}
This works fine for everything but the a tableView. Even by telling the tableView to load on the main thread like so
DispatchQueue.main.async{
self.tableView.reloadData()
}
Didn't change anything. At this point the alerts has values.
I don't mind refactoring the entire thing if need be so any ideas, thoughts or info of how I can get this to work is appreciated. If more info is needed just let me know.
--
Here the table delegate and source defined
class RightViewController : UIViewController, UITableViewDataSource, UITableViewDelegate
and from front end assigned to the uicontroller (its calle Alerts Scene). Forgot to mention that if I do the api call directly in the right controller it works fine but I'm trying to reduce api calls so am refactoring this.
Here are the methods. Sorry for the misunderstanding.
//MARK: Tableview delegates
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let count = alerts?.Alerts.count{
return count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let alertD = alerts?.Alerts[indexPath.row] {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "AlertTableViewCell") as! AlertTableViewCell
cell.name.text = alertD.Summary
cell.icon.image = Helpers.listImage24dp(id: alertD.TOA)
cell.selectionStyle = .none
cell.name.textColor = UIColor.blue
return cell
}
return UITableViewCell()
}

Swift View Controller with UITableView sections

I've been searching for awhile without luck. I am trying to find an example of a View Controller with a UITableView that has sections. The examples I've see are all dealing with a Table View Controller which I cannot use as I have need of buttons in the same view which control the content of the table view. Anyone have an example, know of an example or have an idea about to implement such? Thanks.
Edit
I've got a table view in a view controller, get the data from an api call, separate the sections and data in an array of a struct. I then send this to be bound to the table view. Doing so throws
[UIView tableView:numberOfRowsInSection:]: unrecognized selector sent to instance
but I don't understand where the problem is.
Code for the tablview
//MARK: Tableview delegates
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let count = incidentDataSection?.count{
return count
}
return 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (incidentDataSection?.count)! > 0{
return incidentDataSection![section].incidents.count
}
return 0
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return incidentDataSection?[section].title
}
/*
func tableView(tableView: UITableView, iconForHeaderInSection section: Int) -> UIImage? {
return incidentDataSection?[section].icon
}*/
//if clicked, will openn details view passing in the details
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//let incidentDetails = incidentData?[indexPath.row]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let section = incidentDataSection?[indexPath.section] {
let cell = tableView.dequeueReusableCell(withIdentifier: "IncidentTableViewCell") as! IncidentTableViewCell
cell.roadNameLabel.text = section.incidents[indexPath.row].RoadWay
cell.whenLabel.text = section.incidents[indexPath.row].DateCreated
cell.statusLabel.text = section.incidents[indexPath.row].DateCleared
return cell
}
return UITableViewCell()
}
incidentDataSection is an array of a struct which has the section title and the different items.
Answer
Though I received some fairly good feedback, the cause was actually a typo. Looking closely at
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return incidentDataSection?[section].title
}
you'll notice the problem is that there is no underscore before tableView:. What was happening is that the datasource and delegate were skipping over the functions since with and without call different protocols in swift 3. Thanks to thislink I was able to figure out the cause. My bad for forgetting to mention this was in Swift 3. Might had saved everyone some time.
You need a tableview instance in your view controller.
Implement the protocols UITableViewDelegate, UITableViewDataSource in your view controller as a UITableViewController.
Don't forget bind the tableview in XIB with tableview in the class.
Look this sample:
class Sample01ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
tableView?.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
self.tableView?.reloadData()
}
// ...
You have the required methods implemented, however it sounds like you need to "subclass" or "subcribe" to the UITableView's delegate and dataSource. By using:
class MyViewController : UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView : UITableView!
}
Now that you have those protocols you will need to set your tableView's delegate and dataSource to your viewController. You can do this using storyboard by drag and drop, or inside of your viewDidLoad() which is what I always do because it is easy for other developers to see from the start of opening your code where your delegate and dataSources are assigned to. Using:
#override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
Then your delegate methods and dataSource methods in your viewcontroller will be called for that tableView. Then you can add the IBOutlets to UIButton/UILabel/UISwitch, etc... and do what you will with your ViewController without being limited to simply using a table view inside of that view controller. I Almost always use this methods when using UITableViews/UICollectionViews even if I set the tableView/collectionView to be the size of the whole view because I like the freedom of using a UIViewController over a UITableViewController/UICollectionViewController.
*Note numberOfRows() is not required but I always override it as well, just kind of a habit at this point. Also you sound new to iOS development, so if you aren't already, the next thing I would look into after getting your tableView up and running is pulling your data from your API on a background thread to keep your mainThread open for user response on your UI, DispatchQueue. This is really important if you are displaying images from the API.

change a property from one class in a different class swift (specifically a UILabel)

I'm trying to change the text of a UILabel in a UITableView (a property of one class,) from inside another class, but I'm having trouble. The code looks like this (The problem is at the end, in the didSelectRowAtIndexPath method of myDataSource)
The View Controller
class ViewController: UIViewController
{
//MARK: Properties
#IBOutlet weak var myTableView: UITableView!
#IBOutlet weak var myLabel: UILabel!
//needed so data from myDataSource is retained by ViewController and not just thrown away
let importedDataSource = myDataSource()
override func viewDidLoad()
{
super.viewDidLoad()
myTableView.dataSource = myDataSource()
myTableView.delegate = myDataSource()
}
override func didReceiveMemoryWarning()
{super.didReceiveMemoryWarning()}
}
The UITableViewDataSource and Delegate
class myDataSource: NSObject, UITableViewDataSource, UITableViewDelegate
{
let cellIdentifier = "myTableViewCell"
let myArray = ["Label one", "Label two", "Label three"]
//MARK: TableViewDataSource
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{return 1}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{return myArray.count}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! myTableViewCell
cell.myCellLabel.text = myArray[indexPath.row]
return cell
}
//MARK:TableView Delegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
//This Line Here returns the error
ViewController().myLabel.text = "test"
//I want the end product to be ViewController().myLabel.text = myArray[indexPath.row], but left it as "test" just to simplify and isolate the error
}
}
(there's also a UITableViewCell class called myTableViewCell, but I left it out to be shorter)
Running returns "fatal error: unexpectedly found nil while unwrapping an Optional value" upon selecting one of the rows. How should I call myLabel so as to avoid this problem? I've tried hooking it up as an #IBOutlet inside myDataSource by ctrl-dragging it from the storyboard, but as I expected, it only lets me connect it to the view controller.
sorry, the code is a bit confusing with the two similarly named labels. I'm trying to call the myLabel var created in the ViewController (first piece of code), not the myCellLabel created in myTableViewCell (not shown)
The reason you are seeing the message "fatal error: unexpectedly found nil while unwrapping an Optional value" is because you are trying to assign a value to a property of nil.
In the implementation of your UITableViewDelegate method tableView(_:didSelectRowAtIndexPath:) the statement:
ViewController().myLabel.text = "test"
creates a new instance of your ViewController class (that is what ViewController() does)
accesses the myLabel property of the new instance (recall that in your declaration of the class you defined this property to be of type UILabel!, an implicitly-unwrapped optional UILabel, and implicitly-unwrapped optionals begin life as nil until they are assigned a value)
tries to assign "test" to the text property of myLabel - but as we just noted, myLabel is nil and this is why you are receiving the error message
EDIT
In an effort to assist you in your stated goal of "trying to change the text of a UILabel" (that label being the property myLabel of your ViewController instance), the route I would recommend is:
have your ViewController class adopt the UITableViewDelegate protocol
implement your UITableViewDelegate protocol methods inside of your ViewController class, where they will have direct access to the myLabel property
once you've done this, your implementation of that delegate method (now residing inside of your ViewController class) would look like:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
myLabel.text = myTableView.dataSource.myArray[indexPath.row]
}
...and while this will work, even this can be improved - I recommend you check out Apple's Table View Programming Guide
furthermore, unless there is a compelling reason not to, I also recommend that you have your ViewController class act as your UITableViewDataSource - from the Table View Programming Guide: "[t]he class creating the table view typically makes itself the data source and delegate by adopting the UITableViewDataSource and UITableViewDelegate protocols" (see this section)
You need to get the actual table view cell that was tapped in order to access it's variables. Use cellForRowAtIndexPath. It'll look something like this:
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
if let myCell = tableView.cellForRowAtIndexPath(indexPath) as? myTableViewCell {
myCell.myLabel.text = "test"
}
}

Resources