Error While Loading Table After Swift 2 Upgrade - IOS - ios

When I try to make a dynamic table view with the code below I get the error "unexpectedly found nil while unwrapping an Optional value". Any ideas? I checked the array with print() but they are not empty.
import UIKit
class mainVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var resultsTable: UITableView!
var resultsNameArray = [String]()
var resultsAlloCommentArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
let theWidth = view.frame.size.width
let theHeight = view.frame.size.height
resultsTable.frame = CGRectMake(0, 0, theWidth, theHeight)
refreshResults()
}
func refreshResults() {
resultsNameArray.removeAll(keepCapacity: false)
resultsAlloCommentArray.removeAll(keepCapacity: false)
let query = PFQuery(className: "posts")
query.addDescendingOrder("createdAt")
query.includeKey("relUserPointer")
query.limit = 10
query.findObjectsInBackgroundWithBlock {
(objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
for object in objects! {
self.resultsNameArray.append(object.objectForKey("profileName") as! String)
self.resultsAlloCommentArray.append(object.objectForKey("relUserPointer")!.objectForKey("settingAllowComment") as! String)
}
}
print(self.resultsNameArray)
print(self.resultsAlloCommentArray)
self.resultsTable.reloadData()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
}
override func viewDidAppear(animated: Bool) {
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 127
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//here I get the error in the dequeueReusableCellWithIdentifier
let cell:mainCell = tableView.dequeueReusableCellWithIdentifier("Cell") as! mainCell
cell.profileLbl.setTitle(self.resultsNameArray[indexPath.row], forState: UIControlState.Normal)
return cell
}
}

Your view controller class declaration says this:
class mainVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
So this is a UIViewController - not a UITableViewController.
Therefore, the cells cannot come out of the storyboard as prototype cells. Only a UITableViewController can do that.
So, either you must make this a UITableViewController (here and in the storyboard), or else you must get the cells from somewhere else by calling registerClass:... or registerNib:... on your table view beforehand.
Finally, and most important, you are calling the wrong method here:
tableView.dequeueReusableCellWithIdentifier("Cell")
No. You should call
tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath)
That way, you will always get a cell. The way you are doing it, you can get nil (as you've discovered). You must then make the cell yourself, which you are failing to do; you are not even testing for nil, which is why you are crashing.
One last thing. This has nothing to do with iOS 9 or Swift 2.0. If you think it does, you're just fooling yourself. Your code would have failed in exactly the same way in iOS 8 and Swift 1.2.

Try changing this line :
let cell:mainCell = tableView.dequeueReusableCellWithIdentifier("Cell") as! mainCell
to
let cell:mainCell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as! mainCell

Related

My custom cells are not showing up in my tableview

So I have been trying to get my custom cells to show up on this tableview, but I am not sure as to why they are not showing up
I have already checked other stack overflow questions and tried their fixes, to no avail. Please ignore the aws stuff as you can see I have the text hard coded so I can just get them to appear for now.
This is the code within the class holding the tableview
import Foundation
import AWSDynamoDB
import AWSCognitoIdentityProvider
import UIKit
// this will be the main feed class showing the user data
class UserDetailTableViewController : UITableViewController {
// attributes for the custome cell
#IBOutlet weak var testing: UITextField!
#IBOutlet var Table: UITableView!
var response: AWSCognitoIdentityUserGetDetailsResponse?
var user: AWSCognitoIdentityUser?
var pool: AWSCognitoIdentityUserPool?
var questiondata : Array<Phototext> = Array()
override func viewDidLoad() {
tableView.delegate = self
tableView.dataSource = self
super.viewDidLoad()
self.pool = AWSCognitoIdentityUserPool(forKey: AWSCognitoUserPoolsSignInProviderKey)
if (self.user == nil) {
self.user = self.pool?.currentUser()
}
// grabbing data from our aws table
updateData()
self.refresh()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setToolbarHidden(true, animated: true)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setToolbarHidden(false, animated: true)
}
#IBAction func Questions(_ sender: Any) {
performSegue(withIdentifier: "ask", sender: self)
}
// MARK: - IBActions
#IBAction func signOut(_ sender: AnyObject) {
self.user?.signOut()
self.title = nil
self.response = nil
self.refresh()
}
// reloads the prior view
func refresh() {
self.user?.getDetails().continueOnSuccessWith { (task) ->
AnyObject? in
DispatchQueue.main.async(execute: {
self.response = task.result
self.title = self.user?.username
// saving the user name from the main menu
username123 = self.user?.username! ?? "broken"
})
return nil
}
}
// function that calls to our aws dynamodb to grab data from the
// user
//and re update questions
// the array list
func updateData(){
let scanExpression = AWSDynamoDBScanExpression()
scanExpression.limit = 20
// testing to grabt the table data upon startup
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
dynamoDBObjectMapper.scan(Phototext.self, expression:
scanExpression).continueWith(block: {
(task:AWSTask<AWSDynamoDBPaginatedOutput>!) -> Any? in
if let error = task.error as NSError? {
print("The request failed. Error: \(error)")
} else if let paginatedOutput = task.result {
// passes down an array of object
for Photo in paginatedOutput.items as! [Phototext] {
// loading in the arraylist of objects
// adding the objects to an arraylist
self.questiondata.append(Photo)
}
DispatchQueue.main.async {
//code for updating the UI
}
}
return ()
})
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// returning the number of rows
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:
"Questionpost", for: indexPath) as! QuestionCell
cell.QuestionText.text = "call it"
cell.Subject.text = "a day"
return cell
}
}
}
Here is the code for the QuestionCell class
import UIKit
class QuestionCell: UITableViewCell {
#IBOutlet weak var Subject: UILabel!
#IBOutlet weak var QuestionText: UITextView!
}
The cell class is called QuestionCell and the identifier I left on the cell in the storyboard is Questionpost
Here is a photo of my story board:
I have fixed it by declaring an extension with the proper types.
extension UserDetailTableViewController: UITableViewDataSource,UITableViewDelegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// returning the number of rows
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Questionpost", for: indexPath) as! QuestionCell
cell.QuestionText.text = "call it"
cell.Subject.text = "a day"
return cell
}}
good explanation of what's going on, you need to conform to the UITableViewDataSource and UITableViewDelegate when you inbed a tableview.
Redundant conformance of TableView to protocol UITableViewDataSource with Xib Files

Not using reusable cell in UITableView with CollectionView in each cell

I have a UITableView and in its prototype cell have a UICollectionView.
MainViewController is delegate for UITableView and
MyTableViewCell class is delegate for UICollectionView.
On updating each TableViewCell contents I call cell.reloadData() to make the collectionView inside the cell reloads its contents.
When I use reusable cells, as each cell appears, it has contents of the last cell disappeared!. Then it loads the correct contents from a URL.
I'll have 5 to 10 UITableViewCells at most. So I decided not to use reusable cells for UITableView.
I changed the cell creation line in tableView method to this:
let cell = MyTableViewCell(style: .default, reuseIdentifier:nil)
Then I got an error in MyTableViewCell class (which is delegate for UICollectionView), in this function:
override func layoutSubviews() {
myCollectionView.dataSource = self
}
EXC_BAD_INSTRUCTION CODE(code=EXC_I386_INVOP, subcode=0x0)
fatal error: unexpectedly found nil while unwrapping an Optional value
MyTableViewCell.swift
import UIKit
import Kingfisher
import Alamofire
class MyTableViewCell: UITableViewCell, UICollectionViewDataSource {
struct const {
struct api_url {
static let category_index = "http://example.com/api/get_category_index/";
static let category_posts = "http://example.com/api/get_category_posts/?category_id=";
}
}
#IBOutlet weak var categoryCollectionView: UICollectionView!
var category : IKCategory?
var posts : [IKPost] = []
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
if category != nil {
self.updateData()
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override func layoutSubviews() {
categoryCollectionView.dataSource = self
}
func updateData() {
if let id = category?.id! {
let url = const.api_url.category_posts + "\(id)"
Alamofire.request(url).responseObject { (response: DataResponse<IKPostResponse>) in
if let postResponse = response.result.value {
if let posts = postResponse.posts {
self.posts = posts
self.categoryCollectionView.reloadData()
}
}
}
}
}
internal func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "postCell", for: indexPath as IndexPath) as! MyCollectionViewCell
let post = self.posts[indexPath.item]
cell.postThumb.kf.setImage(with: URL(string: post.thumbnail!))
cell.postTitle.text = post.title
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//You would get something like "model.count" here. It would depend on your data source
return self.posts.count
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
}
MainViewController.swift
import UIKit
import Alamofire
class MainViewController: UITableViewController {
struct const {
struct api_url {
static let category_index = "http://example.com/api/get_category_index/";
static let category_posts = "http://example.com/api/get_category_posts/?category_id=";
}
}
var categories : [IKCategory] = []
override func viewDidLoad() {
super.viewDidLoad()
self.updateData()
}
func updateData() {
Alamofire.request(const.api_url.category_index).responseObject { (response: DataResponse<IKCategoryResponse>) in
if let categoryResponse = response.result.value {
if let categories = categoryResponse.categories {
self.categories = categories
self.tableView.reloadData()
}
}
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return self.categories.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.categories[section].title
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let cell = tableView.dequeueReusableCell(withIdentifier: "CollectionHolderTableViewCell") as! MyTableViewCell
let cell = MyTableViewCell(style: .default, reuseIdentifier:nil)
cell.category = self.categories[indexPath.section]
cell.updateData()
return cell
}
}
MyCollectionViewCell.swift
import UIKit
class MyCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var postThumb: UIImageView!
#IBOutlet weak var postTitle: UILabel!
var category : IKCategory?
}
Why not reusing cells caused this? Why am I doing wrong?
There are a few things to do that should get you up to speed.
First, uncomment the line that uses reusable cells and remove the line of code that creates the non-reusable cells. It is safe to use reusable cells here.
Second, in MyTableViewCell, set the dataSource for the collection view right after the super.awakeFromNib() call. You only need to set the dataSource once, but layoutSubviews() will potentially get called multiple times. It's not the right place to set the dataSource for your needs.
override func awakeFromNib() {
super.awakeFromNib()
categoryCollectionView.dataSource = self
}
I have removed the call to updateData() from awakeFromNib(), as you are already calling it at cell creation. You can also delete the layoutSubviews() override, but as a general rule, you should be careful to call super.layoutSubviews() when overriding it.
Lastly, the reason the posts seemed to re-appear in the wrong cells is that the posts array wasn't being emptied as the cells were reused. To fix this issue, add the following method to MyTableViewCell:
func resetCollectionView {
guard !posts.isEmpty else { return }
posts = []
categoryCollectionView.reloadData()
}
This method empties the array and reloads your collection view. Since there are no posts in the array now, the collection view will be empty until you call updateData again. Last step is to call that function in the cell's prepareForReuse method. Add the following to MyTableViewCell:
override func prepareForReuse() {
super.prepareForReuse()
resetCollectionView()
}
Let me know how it goes!

cellForRowAtIndexPath and numberOfRowsInSection conflicting in tableView

I am creating an app that is retrieving data from Firebase. In my 'MealViewController' I have a TableView that has the view controller as it's delegate and data source. I am getting the issue "Type 'MealViewController" does not conform to protocol 'UITableViewDataSource' because it requires both :numberOfRowsInSection: and :cellForRowAtIndexPath: . However, when I add both, another issue appears - 'Definition conflict with previous value'. I've looked through all the Stack Overflow issues related to this, and no luck has been had. Here's my View Controller:
class MealViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var bgImage: UIImageView?
var image : UIImage = UIImage(named: "pizza")!
#IBOutlet weak var blurEffect: UIVisualEffectView!
#IBOutlet weak var mealTableView: UITableView!
var items = [MealItem]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
bgImage = UIImageView(image: image)
bgImage?.contentMode = .ScaleAspectFill
bgImage!.frame = view.layer.bounds
self.view.addSubview(bgImage!)
//self.bgImage?.addSubview(blurEffect)
//bgImage!.bringSubviewToFront(blurEffect)
view.bringSubviewToFront(blurEffect)
mealTableView.layer.cornerRadius = 5.0
mealTableView.layer.borderColor = UIColor.whiteColor().CGColor
mealTableView.layer.borderWidth = 0.5
let ref = Firebase(url: "https://order-template.firebaseio.com/grocery-items")
mealTableView.delegate = self
mealTableView.dataSource = self
// MARK: UIViewController Lifecycle
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(items.count)
return items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> MealsCellTableViewCell { //issue occurs here
let groceryItem = items[indexPath.row]
if let cell = mealTableView.dequeueReusableCellWithIdentifier("ItemCell") as? MealsCellTableViewCell {
cell.configureCell(groceryItem)
// Determine whether the cell is checked
self.mealTableView.reloadData()
return cell
}
}
func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// [1] Call the queryOrderedByChild function to return a reference that queries by the "completed" property
ref.observeEventType(.Value, withBlock: { snapshot in
var newItems = [MealItem]()
for item in snapshot.children {
let mealItem = MealItem(snapshot: item as! FDataSnapshot)
newItems.append(mealItem)
}
self.items = newItems
self.mealTableView.reloadData()
})
}
func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
}
func willAnimateRotationToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
}
}
override func willAnimateRotationToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
bgImage = UIImageView(image: image)
bgImage?.contentMode = .ScaleAspectFill
bgImage!.frame = view.layer.bounds
self.view.addSubview(bgImage!)
view.bringSubviewToFront(blurEffect)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: UITableView Delegate methods
}
The cellForRowAtIndexPath should look like this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "ItemCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! MealsCellTableViewCell
let groceryItem = self.items[indexPath.row]
cell.configureCell(groceryItem)
return cell
}
Note that the returned cell is a MealsCellTableViewCell which is a subclass of UITableViewCell so it conforms to that class.
Don't change the function definition as that will make it not conform to what the delegate protocol specifies.
Here's a link to the Apple documentation for the specific implementation of custom tableView cells for reference.
Create a Table View
The problem is that your view controller's conformance to UITableViewDatasource cellForRowAtIndexPath method is not right. You should refactor your implementation of cellForRowAtIndexPath method like so:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let groceryItem = items[indexPath.row]
guard let cell = tableView.dequeueReusableCellWithIdentifier("ItemCell") as? MealsCellTableViewCell else {
fatalError("No cell with identifier: ItemCell")
}
cell.configureCell(groceryItem)
return cell
}
You also need to move the datasource methods out of viewDidLoad method.
You return MealsCellTableViewCell instead of UITableViewCell in cellForRowAtIndexPath method, that's the reason.

I have set value for cell of UITableView but why it didn't display?

I have created a UITableView and a UITableVIewCell in Main.storyboard and set it's dataSource and delegate to ViewController .Why UITableView didn't display texts when I run the code.
Another question is that does UITableView load before ViewLoad? If not why in func didRecieveResults() the Array of tableData can achieve datas but in func tableView() it was nil
The whole codes as following
import UIKit
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate,HttpProtocol {
#IBOutlet weak var tv: UITableView!
#IBOutlet weak var iv: UIImageView!
#IBOutlet weak var playTime: UILabel!
#IBOutlet weak var progressView: UIProgressView!
var eHttp:HttpController = HttpController()
var tableData:NSArray = NSArray()
var channelData:NSArray = NSArray()
override func viewDidLoad() {
super.viewDidLoad()
eHttp.delegate = self
eHttp.onSearch("http://www.douban.com/j/app/radio/channels")
eHttp.onSearch("http://douban.fm/j/mine/playlist?channel=0")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
println("tableData.count:\(channelData)")
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell!{
let cell = UITableViewCell(style:UITableViewCellStyle.Subtitle,reuseIdentifier:"douban")
let rowData:NSDictionary = self.tableData[indexPath.row] as! NSDictionary
cell.textLabel!.text = "hehehehe"//rowData["title"] as! String
cell.detailTextLabel!.text = "adasdasda"//rowData["artist"] as! String
return cell
}
func didRecieveResults(results:NSDictionary){
if (results["song"] != nil){
self.tableData = results["song"] as! NSArray
println(tableData)
}else if (results["channels"] != nil){
self.channelData = results["channels"] as! NSArray
// println(channelData)
}
}
}
As Lukas points out, you need to return the UITableViewCell at the end of the method.
In fact, what you posted shouldn't even compile, so I'm wondering if you posted your sample code incorrectly.
The first thing to try, and actually return the cell, update your code to:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell!
{
let cell = UITableViewCell(style:UITableViewCellStyle.Subtitle,reuseIdentifier:"douban")
let rowData:NSDictionary = self.tableData[indexPath.row] as! NSDictionary
cell.textLabel!.text = "hehehehe"//rowData["title"] as! String
cell.detailTextLabel!.text = "adasdasda"//rowData["artist"] as! String
// YOU ARE MISSING THIS LINE
return cell
}
Also ensure that you UITableViewDatasource is set properly, and that the required methods are functioning. Specifically, both numberOfRowsInSection and numberOfSectionsInTableView need to be returning values greater than 0. (In the code you posted, you are missing numberOfSectionsInTableView)
As Lukas said in a comment, you should make sure you return a value from your cellForRowAtIndexPath method otherwise it will refuse to build. If you've done that and you still don't see any cells, it's probably because either numberOfRowsInSection or numberOfSectionsInTableView are returning 0, so you should make sure they return a positive integer.

UITextField and UITableView on a single view controller

I'm trying to make a view controller that has one text field that populates the tableview below, ideally the user will be able to continue to add to the tableview without jumping between two views.
I previously had it working with the text field on one view that populates a UITableView and used prepareForSegue to push the data to the table, but I haven't been able to get it to work with just one view.
Can anyone please point out where I'm going wrong or push me to a tutorial / documentation to help?
Edit: Clarity
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
#IBOutlet var tableView: UITableView!
#IBOutlet weak var textField: UITextField!
var items: [String] = ["Pls", "work", "pls", "work", "pls"]
var foodGroup: FoodGroup = FoodGroup(itemName:"")
//var foodGroup: [FoodGroup] = []
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel.text = self.items[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("Selected cell #\(indexPath)")
}
func addFood(sender: AnyObject!) {
if (countElements(self.textField.text) > 0) {
self.foodGroup = FoodGroup(itemName: self.textField.text)
}
}
#IBAction func addFoodToList() {
let source = FoodGroup
let foodGroup:FoodGroup = source.foodGroup
if foodGroup.itemName != "" {
self.foodGroup.append(foodGroup)
self.tableView.reloadData()
}
}
}
It seems like your intention here is to have your dataSource be an array of FoodGroup objects. If this is indeed the case you can get rid of your foodGroup instance variable and update your items definition to be like so:
var items = [FoodGroup]()
then in addFoodToList:
if self.textField.text != "" {
let foodGroup = FoodGroup(itemName: self.textField.text)
self.items.append(foodGroup)
self.tableView.reloadData()
}
and finally in cellForRowAtIndexPath:
var cell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
let foodGroup = self.items[indexPath.row] as FoodGroup
cell.textLabel.text = foodGroup.itemName
return cell
Also I don't quite see the intention of your the addFood(sender: AnyObject!) function. Looks like cruft. I would get rid of it. Good luck!

Resources