I have encountered an error in swift when attempting to create a tableview made up of custom cells dependent upon a set of conditions.
Here is my code:
var tableData: [String] = []
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
// number of rows in table view
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableData.count
}
// create a cell for each table view row
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let phonenocell:MyCustomCell = self.tableView.dequeueReusableCellWithIdentifier("phonecell", forIndexPath: indexPath) as! MyCustomCell
let pincell:SocialCell = self.tableView.dequeueReusableCellWithIdentifier("socialcell", forIndexPath: indexPath) as! SocialCell
let fbcell:FacebookCell = self.tableView.dequeueReusableCellWithIdentifier("facebookcell", forIndexPath: indexPath) as! FacebookCell
let snapcell:SnapchatCell = self.tableView.dequeueReusableCellWithIdentifier("snapchatcell", forIndexPath: indexPath) as! SnapchatCell
let twitcell:TwitterCell = self.tableView.dequeueReusableCellWithIdentifier("twittercell", forIndexPath: indexPath) as! TwitterCell
let instacell:InstagramCell = self.tableView.dequeueReusableCellWithIdentifier("instagramcell", forIndexPath: indexPath) as! InstagramCell
if tableData.contains("Number") {
return phonenocell
}
if tableData.contains("Social") {
return pincell
}
if tableData.contains("Facebook") {
return fbcell
}
if tableData.contains("Snapchat") {
return snapcell
}
if tableData.contains("Twitter") {
return twitcell
}
if tableData.contains("Instagram") {
return instacell
}
}
When attempting to build and run I get a build failed with the following fault:
"Missing Return in a function expected to return 'UITableViewCell'
I have been over and over my code but I honestly cannot see where I am going wrong...
Any help would be greatly appreciated!
You need to return cell for sure.
You already do in conditions, but in case none of your condition statements would success, your return call wouldn't be fired.
Appending, for example:
return phonenocell
to the end of the function, should be quick fix for your code. It ensures, that the function will return a cell (that is mandatory).
My data source is the array tableData. This is constructed on the previous view as: #IBAction func switch1Toggled(sender: UISwitch) { if mySwitch1.on { fbTextBox.text = "Selected" dataArray.append("Facebook")
And this may be the main issue:
Assuming, that you choose 'facebook' and that you reload your tableView, every row will pass the first condition as it IS contained.
You should put this in your method:
//assuming your data source contains multiple members, and your numberOfRowsInSections... method return tableData.count, you need to get each item for each row:
let currentTag = tableData[indexPath.row]
if (currentTag == "something") { //e.g. Facebook
let somethingcell:MySomethingCell = ...
self.tableView.dequeueReusableCellWithIdentifier("somethingcell", forIndexPath: indexPath) as! MySomethingCell
return somethingcell
} else if {
...
}
return emptycell //this line is just for the case, when no of your conditions will pass and you don't catch all the situations...
maybe your array elements doesn't match the condition, it's better to return default value instead of ur conditions failed
Related
I'm using two TableViews in a ViewController but I get this error when it gets to the second TableViewCell, cartProductCell. They both have custom classes, and outlets, as it was the problem for many in other posts. Is the first time I'm doing this and I can't find a solution for this. May it be just because I'm using custom classes for the cells? In the tutorials I found about two TableViews weren't used custom classes.
In the Storyboard editor everything is connected well and identifiers are both correct.
Here's the function:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell?
// if tableView == self.worksTableView && CartViewController.bookedWoksArray.count > 0 {
if tableView == self.worksTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "cartWorkCell", for: indexPath as IndexPath) as! CartWorkTableViewCell
cell.workImageView.image = CartViewController.bookedWoksArray[indexPath.row].0
cell.workNameLabel.text = CartViewController.bookedProductsArray[indexPath.row].1
cell.workPriceLabel.text = CartViewController.bookedWoksArray[indexPath.row].2
} // else {return}
// else if tableView == self.worksTableView && CartViewController.bookedProductsArray.count > 0 {
if tableView == self.worksTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "cartProductCell", for: indexPath as IndexPath) as! CartProductTableViewCell
cell.cartProductImageView.image = CartViewController.bookedProductsArray[indexPath.row].0
cell.cartProductNameLabel.text = CartViewController.bookedProductsArray[indexPath.row].1
cell.cartProductPriceLabel.text = CartViewController.bookedProductsArray[indexPath.row].2
} //else {return}
return cell!
}
As usual many thanks
After a few tries and after fixing a silly mistake I finally made it work by assigning the value of custom cells to cell and the function's code is now:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell?
if tableView == self.worksTableView {
let workCell = tableView.dequeueReusableCell(withIdentifier: "cartWorkCell", for: indexPath) as! CartWorkTableViewCell
workCell.workImageView.image = CartViewController.bookedWoksArray[indexPath.row].0
workCell.workNameLabel.text = CartViewController.bookedWoksArray[indexPath.row].1
workCell.workPriceLabel.text = CartViewController.bookedWoksArray[indexPath.row].2
cell = workCell
}
if tableView == self.productsTableView{
let productCell = tableView.dequeueReusableCell(withIdentifier: "cartProductCell", for: indexPath) as! CartProductTableViewCell
productCell.cartProductImageView.image = CartViewController.bookedProductsArray[indexPath.row].0
productCell.cartProductNameLabel.text = CartViewController.bookedProductsArray[indexPath.row].1
productCell.cartProductPriceLabel.text = CartViewController.bookedProductsArray[indexPath.row].2
cell = productCell
}
return cell!
}
I am trying to access an array's indexPath inside a function to update this array's data but I don't know how to pass the indexPath as a parameter (espacially what to pass when calling) to the function or if this is even the solution.
I included cellForRowAt to illustrate how this function access indexPath.
var cryptosArray: [Cryptos] = []
extension WalletTableViewController: UITableViewDelegate, UITableViewDataSource, CryptoCellDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let crypto = cryptosArray[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! WalletTableViewCell
cell.setCrypto(crypto: crypto)
cell.delegate = self
cell.amountTextField.delegate = self
return cell
}
func cellAmountEntered(_ walletTableViewCell: WalletTableViewCell) {
if walletTableViewCell.amountTextField.text == "" {
return
}
let str = walletTableViewCell.amountTextField.text
let crypto = cryptosArray[indexPath.row] //<---- How to do that?
crypto.amount = walletTableViewCell.amountTextField.text
//Then update array's amount value at correct index
walletTableViewCell.amountTextField.text = ""
}
}
Instead of hacking something, simply ask the tableView to tell you the indexPath of the given cell:
// use indexPath(for:) on tableView
let indexPath = tableView.indexPath(for: walletTableViewCell)
// then you can simply use it
let crypto = cryptosArray[indexPath.row]
UITableView.indexPath(for:) documentation says:
Returns an index path representing the row and section of a given table-view cell.
And this is exactly what you want, you don't want to hack the indexPath to the cell. indexPath should be taken care of by the tableView, not the cell. Ideally, cell should be completely oblivious of its indexPath.
Always try to use the standard way to solve your problems. In general, when you are trying to solve something, I would recommend you to first look at the documentation of the UITableView, there are many useful methods there.
if you want to get index path.row when user clicked on cell , you should get index path.row when user clicked and then use this to your func
for ex :
var indexrow : int = 0
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// table cell clicked
indexrow = indexPath.row
}
func cellAmountEntered(_ walletTableViewCell: WalletTableViewCell) {
if walletTableViewCell.amountTextField.text == "" {
return
}
let str = walletTableViewCell.amountTextField.text
let crypto = cryptosArray[indexrow]
crypto.amount = walletTableViewCell.amountTextField.text
//Then update array's amount value at correct index
walletTableViewCell.amountTextField.text = ""
}
I have a table that displays the results of an http request.
Sometimes the application crashes because the table is displayed before the query is finished .. How can the application no crash?
The table :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! MyCustomCell
for i in 0...nameOfRoutesStart.count {
if (indexPath.row == i) {
cell.originLabel.text = nameOfRoutesStart[i]
cell.destinationLabel.text = nameOfRoutesEnd[i]
let id = driver[i]
self.userTasks.user(driverId: id, completionHandler: { (status, success) -> Void in
if success {
cell.driverLabel.text = self.userTasks.username
}
})
cell.textLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
}
}
return cell
}
I make the request in another function
Thanks
Extract your asynchronous code from the tableview methods to your viewDidLoad. Save the data you retrieve in a variable declared in your controller and call .reloadData() on your tableview when your fetch is finished.
I have three different types of custom UITableCells. I have an if statement that sets them up:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if somePosts[indexPath.row].typeOfPost == .linkPost {
let cell: LinkTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "linkTableViewCell") as! LinkTableViewCell
} else if somePosts[indexPath.row].typeOfPost == .picturePost {
let cell: PictureTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "pictureTableViewCell") as! PictureTableViewCell
} else if somePosts[indexPath.row].typeOfPost == .textPost {
let cell: TextTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "textTableViewCell") as! TextTableViewCell
} else {
print("Type of post is not link, picture, or text")
}
}
Each of the custom cells has similar labels such as title and time. I would like to set these labels using the same line of code, such as:
cell.titleLabel.text = "Some title here"
However, in this example, I get an error saying I am using an unresolved identifier "cell," obviously because my variables are being declared non-globally. Is there a way around this since swift is strongly typed? Thanks!
Make a protocol that your TableViewCell classes extend, and store cell as a variable of that type.
protocol MyTableViewCell {
var titleLabel: UILabel { get }
// ...
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier: String
switch somePosts[indexPath.row].typeOfPost {
case .linkPost: identifier = "linkTableViewCell"
case .picturePost: identifier = "pictureTableViewCell"
case .textPost: identifier = "textTableViewCell"
default: fatalError("Type of post is not link, picture, or text")
}
guard let cell = self.tableView.dequeueReusableCell(withIdentifier: identifier) as? MyTableViewCell else {
fatalError("Cell isn't castable to MyTableViewCell")
}
cell.titleLabel.text = "Some title here"
// ...
}
You have three basic solutions.
Repeat cell.text = ... inside each block. But this isn't what you really want as stated in your question.
Have your three custom cell classes all extend a common base class. Have this base class define any common properties.
Define a protocol with the common properties and have each of your custom cell classes conform to the protocol.
For options 2 and 3 you would declare a variable of the base/protocol type before the first if statement. Then after the whole if/else block, you can assign any of the common properties.
If you need to update any cell type specific properties, you can do that inside the appropriate block as well.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: BaseTableViewCell?
if somePosts[indexPath.row].typeOfPost == .linkPost {
cell = self.tableView.dequeueReusableCell(withIdentifier: "linkTableViewCell") as! LinkTableViewCell
} else if somePosts[indexPath.row].typeOfPost == .picturePost {
cell = self.tableView.dequeueReusableCell(withIdentifier: "pictureTableViewCell") as! PictureTableViewCell
} else if somePosts[indexPath.row].typeOfPost == .textPost {
cell = self.tableView.dequeueReusableCell(withIdentifier: "textTableViewCell") as! TextTableViewCell
} else {
print("Type of post is not link, picture, or text")
}
if let cell = cell {
cell.commonProperty = ...
return cell
} else {
return nil // this shouldn't happen but if it does, you have a bug to fix
}
}
If the subclasses each have their own titleLabel property, you will need to make them all conform to a protocol. Let's call it ConfigurableCell.
protocol ConfigurableCell {
var titleLabel: UILabel { get set }
}
Then, you can initialize your cells all the same way, but declare them as a ConfigurableCell:
var cell: ConfigurableCell? = nil // not set yet
if somePosts[indexPath.row].typeOfPost == .linkPost {
cell = self.tableView.dequeueReusableCell(withIdentifier: "linkTableViewCell") as! LinkTableViewCell
} else if somePosts[indexPath.row].typeOfPost == .picturePost {
cell = self.tableView.dequeueReusableCell(withIdentifier: "pictureTableViewCell") as! PictureTableViewCell
} else if somePosts[indexPath.row].typeOfPost == .textPost {
cell = self.tableView.dequeueReusableCell(withIdentifier: "textTableViewCell") as! TextTableViewCell
}
guard let cell = cell else {
// how to handle this error case is up to you
print("Type of post is not link, picture, or text")
return UITableViewCell()
}
// now, cell is a ConfigurableCell with a titleLabel property, regardless of class
cell.titleLabel.text = "Some title"
Of course, UITableViewCell does have a built-in textLabel property, which you could try to utilize in your cell classes, and then a protocol wouldn't be necessary, because the property is in UITableViewCell.
So I have a Segmented Control that switches between 2 TableViews & 1 MapView inside a MainVC.
I'm able to switch the views in the simulator by adding an IBAction func changedInSegmentedControl to switch which views are hidden while one of them is active.
I created 2 custom TableViewCells with XIBs. I also added tags with each TableView.
My question is how do I add them in cellForRowAtIndexPath?
Currently, my code is:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
// var cell: UITableViewCell
if (tableView.tag == 1) {
cell: CardTableViewCell = tableView.dequeueReusableCellWithIdentifier("CardCell") as! CardTableViewCell
return cell
}
else if (tableView.tag == 2) {
cell: ListTableViewCell = tableView.dequeueReusableCellWithIdentifier("ListCell") as! ListTableViewCell
return cell
}
}
Of course Swift requires a "return cell" for the function outside the If statements. I tried with a var cell: UITableViewCell outside, but run into trouble finishing the dequeuReusableCellWithIdentifier.
Anyone have some idea how to do this? Thanks.
This is how I approached it (Swift 3.0 on iOS 10). I made one tableView with two custom cells (each is their own subclass). The segmented control is on my navigationBar and has two segments: People and Places.
There are two arrays within my class, (people and places) which are the data sources for the two table views. An action attached to the segmentedControl triggers the reload of the table, and the switch statement in cellForRowAtIndex controls which cell from which array is loaded.
I load data into the two data arrays from an API call, the asynchronous completion of which triggers dataLoaded(), which reloads the tableView. Again I don't have to worry about which segment is selected when the table is reloaded: cellForRowAtIndex takes care of loading the correct data.
I initialize a basic cell just as UITableViewCell and then in the case statement I created and configure the custom cell. Then I return my custom type cell at the end, and as long as the reuseIdentifiers and classes are correct in cellForRowAtIndex, the correct cell is initialized and displayed in the tableView.
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var peoplePlacesControl: UISegmentedControl!
fileprivate var placesArray: [PlaceModel]?
fileprivate var usersArray: [UserModel]?
#IBAction func segmentedControlActionChanged(_ sender: AnyObject) {
tableView.reloadData()
switch segmentedControl.selectedSegmentIndex {
case 0:
loadUsersfromAPI()
case 1:
loadPlacesFromAPI()
default:
// shouldnt get here
return
}
}
func dataLoaded() {
switch peoplePlacesControl.selectedSegmentIndex {
case 0: // users
if favoriteUsersArray == nil {
self.tableView.reloadData()
} else {
hideTableViewWhileEmpty()
}
case 1: // places
if placesArray != nil {
self.tableView.reloadData()
} else {
hideTableViewWhileEmpty()
}
default:
return
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch segmentedControl.selectedSegmentIndex {
case 0:
if usersArray != nil {
return usersArray!.count
} else {
return 0
}
case 1: // places
if placesArray != nil {
return placesArray!.count
} else {
return 0
}
default:
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
switch peoplePlacesControl.selectedSegmentIndex {
case 0: // people
let userCell = tableView.dequeueReusableCell(withIdentifier: "MyUserCell", for: indexPath) as! MyUserTableViewCell
if usersArray != nil && indexPath.row < usersArray!.count {
let user = usersArray![indexPath.row]
userCell.configure(user)
userCell.myDelegate = self
}
cell = userCell as MyUserTableViewCell
case 1: // places
let placeCell = tableView.dequeueReusableCell(withIdentifier: "MyPlaceCell", for: indexPath) as! MyPlaceTableViewCell
if favoritePlacesArray != nil && indexPath.row < favoritePlacesArray!.count {
let place = placesArray![indexPath.row]
placeCell.configure(place)
placeCell.myDelegate = self
}
cell = placeCell as MyPlaceTableViewCell
default:
break
}
return cell
}
I have made change in your code.
Use following code
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
if (tableView.tag == 1) {
cell: CardTableViewCell = tableView.dequeueReusableCellWithIdentifier("CardCell") as! CardTableViewCell
return cell
}
cell: ListTableViewCell = tableView.dequeueReusableCellWithIdentifier("ListCell") as! ListTableViewCell
return cell
}