I have a TableView that is embedded into a CollectionView, and I am trying to show relevant data in the TableView that corresponds to the correct CollectionViewCell or IndexPath Item. I tried assigning tag as such: cell.tableView.tag = indexPath.item but it seems to be problematic.
I tried print(tableView.tag) in my collectionViewCell and it printed
2 1 0 3 4 5
but I have 7 collectionViewCells in total so the last tag isn't printing for some reason.
My collectionView is embedded in another TableView already, below is the code in the MasterTableViewCell.swift:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if diningIndexPath.section == 0 {
let cell: FoodCourtCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "foodCourtCell", for: indexPath) as! FoodCourtCollectionViewCell
cell.tableView?.register(UINib(nibName: "RestaurantTableViewCell", bundle: nil), forCellReuseIdentifier: "restaurantCell")
cell.tableView.tag = indexPath.item
//...
return cell
}
}
In the customCollectionViewCell.swift, I have this code for my tableView:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: RestaurantTableViewCell = tableView.dequeueReusableCell(withIdentifier: "restaurantCell", for: indexPath) as! RestaurantTableViewCell
print(tableView.tag)
let currentRestaurant = foodCourts[tableView.tag].childLocations[indexPath.row]
cell.textLabel.text = currentRestaurant.name
//...
return cell
}
Is there any way to fix this, or are there other ways to achieve what I want to do? Any help is appreciated, thanks!
Nesting these entities is always a pain especially when you need to access indexPaths of items later. If if get your problem correctly. One of the solutions I suggest is to store a map (dictionary) of your paths. For a fast access to them. Here's an example of how I managed this in a similar situation:
typealias CollectionIndexPath = IndexPath
typealias TableIndexPath = IndexPath
var indexMap: [CollectionIndexPath: TableIndexPath] = [:]
Now when you need to access some of the items or configure it.
func cellForItemAtIndexPath { ... } {
let cell = { ... }
let cellPath = indexPath
let tablePath = indexMap[cellPath]
let foodCourtForCell = foodCourts[cellPath.item]
let childLocationsForTableView = foodCourtForCell.childLocations
cell.configureWith(court: foodCourtForCell, locations: childLocations)
Now you can manage all the data related to this nested monster from the outside.
Related
I'm making a music app recently, and I'd like to know how to pass data from CollectionView to TableView which has several sections. Here is the home page of my project, and what I want to do is when user tap the image, it will precent another ViewController with the information about the picture. I already know how to present a ViewController by clicking CollectionViewCell inside TableView by delegate, but only if there's only one section.
The thing is that I have 5 sections in this page, and I also have 5 different models for encoding the JSON from API. So how I show the pictures is to send the image urls(from 5 models) to TableViewCell in each section like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: HomeTableViewCell.identifier, for: indexPath) as? HomeTableViewCell else { return UITableViewCell() }
cell.delegate = self
switch homeSections[indexPath.section] {
case .newReleases:
if let newReleases = self.newReleases?.albums.items.map({$0.images[0].url}) {
cell.getPictures = newReleases
}
return cell
case .followSingers:
if let currentlyFollowing = self.currentlyFollowing?.artists.items.map({$0.images[0].url}) {
cell.getPictures = currentlyFollowing
cell.isCircle = true
}
return cell
case .catrgories:
if let categories = self.musicCategory?.playlists.items.map({$0.images[0].url}) {
cell.getPictures = categories
}
return cell
case .artists:
if let playlist = self.relatedArtists?.artists.map({$0.images[0].url}){
cell.getPictures = playlist
}
return cell
case .recentlyPlayed:
if let recentlyPlayed = self.recentlyPlayed?.items?.map({$0.track.album.images[0].url}) {
cell.getPictures = recentlyPlayed
}
return cell
}
}
However, when I want to pass the information which the user tap, there's nothing I can pass but indexPath. I've tried to declare the 5 different model types in TableViewCell, but I still don't know which section did the user tap. Does anyone can help? Thanks a lot!
Update:
To make the question more clearly, please refer to the information below.
In this page, I have a TableView and embed a CollectionView in the TableViewCell, and there's only one ImageView in the CollectionViewCell.
The "New Releases", "Currently Following" and "Categories" are the header of TableView. The "New Releases" is the first section, and the "Currently Following" is the second section, and so on.
Here is how I set cellForItem in CollectionView Delegate. It basically converts String to URL, and display the picture on the screen.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeCollectionViewCell.identifier, for: indexPath) as? HomeCollectionViewCell else { return UICollectionViewCell() }
guard let url = URL(string: getPictures[indexPath.row]) else { return UICollectionViewCell() }
cell.myImageView.getImages(url: url)
if isCircle == true {
cell.myImageView.layer.cornerRadius = cell.myImageView.frame.width/2
}
return cell
}
When the user taps the image, it will only trigger the didSelectItemAt in CollectionView. And I can only pass indexPath.row so far.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
delegate?.sendIndexPath(index: indexPath.row)
}
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!
}
This question follows up from this: Use UICollectionViews to create dynamic and multiple features.
I am able to create a static cell which displays the name and image of the recipe similar like this app:
Where I am stuck is creating a dynamic row which changes based on the amount of data inside i.e. utensils or nutritional values like the image below:
I know how to display rows of data on tableView normally. But not sure how to embed it into a section inside a tableView. I attempted to add multiple prototype cells and assign them to a subclass of UITableViewCell's. Then I try to use if statements in my cellForRow but this isn't soling my issue.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FirstCell
//set the data here
cell.recipeTitle.text = recipe.name
cell.imageView.image = UIImage(named: "url")
return cell
}
else if indexPath.row == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell2", for: indexPath) as! SecondCell
//set the data here
return cell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell3", for: indexPath) as! ThirdCell
//set the data here
return cell
}
}
I have also looked at this demonstration: https://medium.com/ios-os-x-development/ios-how-to-build-a-table-view-with-multiple-cell-types-2df91a206429, which is near to what I want to achieve but I have found it quite difficult to adapt to it.
If someone could direct me on how best to approach this or a good example then I would really appreciate it.
First you can't have static cells and dynamic cells in the same tableView. So how do you work around that? Define each of the static cells in the sections they belong in as well as the dynamic cells in the sections they belong to. That, however doesn't look like what you are trying to do. You just want multiple sections in the same tableView, each section with a different list of data
To do this you will need the number of sections so use the tableView(_:numberOfSections:) function.
override func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
You can then(and probably should) give each of those sections a title by initializing an array with the titles in your tableViewController(assuming thats what you are using. It could also just be a tableView).
let headerTitles = ["Nutritional Values", "Utensils", "Ingredients"]
Then use the tableView(_:titleForHeaderInSection:)
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section < headerTitles.count {
return headerTitles[section]
}
return nil
}
Now you can start defining your rows by the sections.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell
if indexPath.section == 0 {
//Setup up the first row
if indexPath.row == 0 {
//I'm not sure where or how you defined First/SecondCell but this may not work depending on those two questions.
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FirstCell
return cell
} else if indexPath.row == 1 {
let cell = Bundle.main.loadNibNamed("StaticCell", owner: self, options: nil)?.first as! StaticCell
return cell
}
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! SecondCell
//setup cell1 n your storyboard to have a label with a tag # of 12(or whatever number you want to use)
//you also want an array with your utensil data accessible here
let label = cell.viewWithTag(12) as! UILabel
label.text = utensilNames[indexPath.row]
return cell
} else if indexPath.section == 2 {
let cellIngredients = tableView.dequeueReusableCell(withIdentifier: "Ingredients", for: indexPath)
tableView.deselectRow(at: indexPath, animated: true)
return cellIngreidents
}
cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
return cell
}
The point here is to use sections then rows to distribute your data.
Just to clarify Section 0 Row 0 - N would be where you're static rows are setup. I found it best to use XIB files subclassing TableViewCell.
Hope this helps.
EDIT So the way I'm looking at the "static" cells is in the first section the xib is the only put exactly where you tell it to be placed. In the example above the first section in the second cell is the
I have a UICollectionView and have two cell types. ShareCell and ShareCellMedia. I have it so that ShareCellMedia is only returned when the property .hasImage is true. However, when implementing the logic in cellforitem at index path, it returns all cells as either ShareCell or ShareCellMedia when in this case, because of the data I'm using one cell should be a media cell while the others are regular.
Below is the code in cell for item at index path
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath :
IndexPath) -> UICollectionViewCell {
let friend = fetchedResultsController.object(at: indexPath) as! Friend
if (friend.lastMessage?.hasImage)! == false {
let mediaCell = collectionView.dequeueReusableCell(withReuseIdentifier: mediaCellId, for: indexPath) as!
ShareCellMedia
mediaCell.cell = friend.lastMessage
return mediaCell
}
else if (friend.lastMessage?.hasImage)! == true {
let regularCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as!
ShareCell
regularCell.cell = friend.lastMessage
return regularCell
}
return UICollectionViewCell()
}
any suggestions?
Are you fetching the correct friend object? As you're using indexPath, not indexPath.row
My cellforitem at index path wasn't the cause of the problem.
Thanks in part #DonMags comment I was able to deduce that the function I used to the messages didn't have the variable .hasImage properly instantiated.
I have currently have two UITableViews populated with contacts for the app. I have one for simply viewing them and editing/deleting and one for searching/picking contacts from a list. However, I'm getting a returned nil value when trying to use the same custom class cell for both UITableViews.
These are my two cellForRowAtIndexPath functions.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("SecondCell") as! ContactCell
let item = contacts[indexPath.row]
cell.meetupLabel?.text = item.fullName
return cell
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("FirstCell") as! ContactCell
let item = contacts[indexPath.row]
cell.label?.text = item.fullName
return cell
}
If the table did not have a cell named FirstCell or SecondCell, the dequeueReusableCellWithIdentifier(_:) method will return nil, and you will need to construct the cell yourself.
// no don't do this.
let cell: ContactCell
if let c = tableView.dequeueReusableCell(withIdentifier: "FirstCell") as? ContactCell {
cell = c
} else {
cell = ContactCell(style: .default, reuseIdentifier: "FirstCell")
}
You should use dequeueReusableCell(withIdentifier:for:), which was introduced in iOS 6, if you would like UIKit to construct the cell for you:
// swift 3
let cell = tableView.dequeueReusableCell(withIdentifier: "FirstCell",
for: indexPath) as! ContactCell
// swift 2
let cell = tableView.dequeueReusableCellWithIdentifier("FirstCell",
forIndexPath: indexPath) as! ContactCell
...
Also, check if you have given the correct reuse-identifiers to the cells correctly in the interface builder.
As you said you are getting nil, my quick guess is that you haven't registered the cell at some point, runs earlier than this cell event. Look at this thread on how to register cell.