I have a collection view. On one of the collection cells, I have a tableview with 5 different custom cells. I am reusing each cell of the table view.
When the table is reloaded, I see new rows added just above the previously loaded cells. This appears like a new table has been added just above the previous table. I got this visual when I see the 3D view of the collection view:
Blue color is one of the 5 cells. Each time the table loads, it has added 5 sets of rows above previously loaded cells. What is happening here and how could this be possible?
override func viewDidLoad() {
super.viewDidLoad()
self.cabinTableView.registerNib(UINib(nibName:"CabinNetNib", bundle:
nil), forCellReuseIdentifier: cabinNetNib)
}
internal func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellDetail = tableViewDataSourceArray[indexPath.row] as? cabinDetailsMO
let cellType = cellDetails?.cellTypeEnum
if cellType == CellType.Net {
let netLabelCell = self.questionsTableView
.dequeueReusableCellWithIdentifier(questionLabelCellIdentifier) as?
NetLabelTableViewCell
netLabelCell!.selectionStyle = UITableViewCellSelectionStyle.None
netLabelCell!.netLabel.attributedText = cellDetails?.attributedQuestionString
return netLabelCell!
}
else if cellType == CellType.Video {
let videoCell = self.cabinTableView.dequeueReusableCellWithIdentifier(cabinVideoCellIdentifier)
as! cabinVideoTableViewCell
videoCell.videoName = cellDetails?.questionVideo
videoCell.loadVideoItem(cellDetails?.questionVideo, videoFields: nil)
videoCell.selectionStyle = UITableViewCellSelectionStyle.None
shouldReloadAllCells = true
image_videoCellIndex = indexPath.row
return videoCell
}
else if (cellType == CellType.captian) {
let captianCell = self.cabinTableView .dequeueReusableCellWithIdentifier(captianCellIdentifier) as!
CaptianTableViewCell
captianCell .selectionStyle = UITableViewCellSelectionStyle.None
captianCell.question_idLabel.text = cellDetails?.contentidName
return aptianCell
}
}
First cell is alone a nib.
Related
I have a tableview with two custom nib cells registered. The cells and their corresponding connected classes are completely independent of each other.
The tableview based on a condition should present either of one of the two cells registered:
The tableview code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//Show only the table cells that have content.
tableView.tableFooterView = UIView(frame: CGRect.zero)
let rowData = FriendsTab.potentialFriendList[indexPath.row]
if (rowData.pendingInvite){
let cell = potentialFriendTableView.dequeueReusableCell(
withIdentifier: requestCellIdentifier, for: indexPath)
as! FriendRequestCell
cell.activityIndicator.isHidden = true
cell.activityIndicator.hidesWhenStopped = true
cell.userName.text = firstName + " " + lastName
return cell
}
else{
let cell = potentialFriendTableView.dequeueReusableCell(
withIdentifier: potentialCellIdentifier, for: indexPath)
as! PotentialFriendCell
cell.activityIndicator.isHidden = true
cell.activityIndicator.hidesWhenStopped = true
cell.userName.text = firstName + " " + lastName
return cell
}
}
The cell registration code (from viewDidLoad):
let potentialFriendXib = UINib(nibName: self.potentialCellIdentifier, bundle: nil)
let requestFriendXib = UINib(nibName: self.requestCellIdentifier, bundle: nil)
self.potentialFriendTableView.register(potentialFriendXib,forCellReuseIdentifier: self.potentialCellIdentifier)
self.potentialFriendTableView.register(requestFriendXib,forCellReuseIdentifier: self.requestCellIdentifier)
For some reason the FriendRequestCell calls awakeFromNib() twice however the PotentialFriendCell works as expected (one single call).
I have searched the few (very old) SO questions on this but they seem to deal with nested (parent child) nibs which this project does not use (they are individual).
Any ideas where I've gone wrong?
All of this is done in InterfaceBuilder. In interface builder, open up your storyboard or xib that contains the tableviewcontroller. Then select to use Content: Dynamic Prototypes, the select 2 for Prototype Cells. You will then be given 2 TableView Cells. The next step is to do exactly what you did for the cells in each xib. Now there is no loading of nib, and no registering. You still have to do the dequeueReusableCell for each cell.
I have a collection view inside a table view cell at row = 1, that is loaded with content from Firebase Database after the cell is already dequeued. Therefore, using AutoDimensions on the cell at heightForRowAt: doesn't work since at that time, there is no content within the collection view. The collection view is anchored to all sides of the cell. What's the best way of updating the table view cell height at that specific index path when the collection view has loaded all of the data. Thanks!
In the VC containing the table view, I've set up a func retrieving a list of users from the DB that is called in viewDidLoad(), and I've set up protocols to reload the collectionView that is located in the tableView cell class.
fileprivate func retrieveListOfUsers() {
print ("fetching users from database")
let ref = Database.database().reference()
guard let conversationId = conversation?.conversationId else {return}
ref.child("conversation_users").child(conversationId).observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionaries = snapshot.value as? [String:AnyObject] else {return}
for (key, _) in dictionaries {
let userId = key
Database.fetchUsersWithUID(uid: userId, completion: { (user) in
self.users.append(user)
DispatchQueue.main.async {
self.reloadDelegate.reloadCollectionView()
}
})
}
}, withCancel: nil)
}
This is the code for dequeuing the cell. I've removed the code for the first cell in this post since it's irrelevant and didn't want to make you read the unnecessary code.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Code for cell in indexPath.row = 0
if indexPath.row == 1 {
let usersCell = tableView.dequeueReusableCell(withIdentifier: usersCellId, for: indexPath) as! UsersTableViewCell
usersCell.backgroundColor = .clear
usersCell.selectionStyle = .none
usersCell.collectionView.delegate = self
usersCell.collectionView.dataSource = self
self.reloadDelegate = usersCell
usersCell.users = users
return usersCell
}
}
If I return UITableView.automaticDimension the collection view doesn't show. It only shows if I set the func to return a constant like 200. I've set estimatedRowHeight and rowHeight in viewDidLoad().
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 {
return UITableView.automaticDimension
}
return UITableView.automaticDimension
}
After you load the data source for the collection view, you can update specific rows in your table like this:
UIView.performWithoutAnimation {
tableView.reloadRows(at: [indexPath], with: .none)
}
For the row height - try to remove the method implementation, it should be enough if you already set a value for estimatedRowHeight.
I have a table view which loads custom table view cells. In the table view cell I have an image view, among other controls. The problem is that the table view stutters while scrolling, even though the images are being read from Cache or documents directory (using either isn't making any difference)
Here is the code for displaying the table view cell:
func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
if cell.isKindOfClass(EventsTableViewCell) {
let eventCell = cell as! EventsTableViewCell
let list = self.eventsList
if list.count > 0 {
let event = list.objectAtIndex(indexPath.row) as! Event
//other cell labels configured here
//imageCache is NSCache instance
if let imageData = self.imageCache.objectForKey(event.name) as? NSData {
let image = UIImage(data: imageData)
eventCell.eventImageView.image = image
}
else if event.imageUrl != "" {
eventCell.setImageToCell(event)
}
}
}
}
func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "eventCell"
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier)
if cell == nil {
let nibCollection = NSBundle.mainBundle().loadNibNamed("EventsTableViewCell",
owner: nil,
options: nil)
cell = (nibCollection as NSArray).objectAtIndex(0) as? EventsTableViewCell
}
cell?.selectionStyle = .None
return cell!
}
In EventsTableViewCell,
func setImageToCell(event: Event) {
//apply placeholder image. This image will be replaced if an image is found for the particular event.
if event.image != nil {
dispatch_async(dispatch_get_main_queue(), {
self.eventImageView.image = event.image
})
}
}
So even though none of the images are being downloaded here, the table view still can't handle the scrolling properly. I am not sure what I am doing wrong. Can the size of the images be an issue here?
I want to create a table view that loads its data from two different data sources and chooses a specific nib depending on which one it is. How can I create a feed that mixes elements from two arrays of objects?
I was thinking about creating an array of objects with the objects being sorted by date. Then, I would check what the type of each object is at my cellforrowatindexpath method and use the nib that corresponds.
Is this the most efficient way of doing this?
Thank you
Creating a single array of objects and dealing with them correctly in cellForRowAtIndexPath is a good approach.
You should register each of your nibs with the table view, probably in viewDidLoad using registerNibForCellReuseIdentifier. Make sure you use a different reuseIdentifier for each, then dequeue the correct type in cellForRowAtIndexPath, once you've determined which type of cell it should be.
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerNib(UINib(nibName: "fooCell", bundle: .mainBundle()), forCellReuseIdentifier: "fooCellIdentifier");
tableView.registerNib(UINib(nibName: "barCell", bundle: .mainBundle()), forCellReuseIdentifier: "barCellIdentifier");
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell?
if (indexPathIsFooCell(indexPath)) {
cell = tableView.dequeueReusableCellWithIdentifier("fooCellIdentifier", forIndexPath: indexPath)
} else {
cell = tableView.dequeueReusableCellWithIdentifier("barCellIdentifier", forIndexPath: indexPath)
}
//customise the cell
return cell!
}
Here is Another Solution which Might help You
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.section == 0 {
var cell = tableView.dequeueReusableCellWithIdentifier("relatedTableViewCell") as? relatedTableViewCell
if cell == nil {
let nib:Array = NSBundle.mainBundle().loadNibNamed("relatedTableViewCell", owner: self, options: nil)
cell = nib[0] as? relatedTableViewCell
}
let info = relatedQuizArr.objectAtIndex(indexPath.row) as! relatedInfo
cell?.relatedImage.sd_setImageWithURL(NSURL(string: info.rqImage!))
cell?.relatedLabel.text = (String: info.rqName!)
cell?.rQuizView.layer.shadowOffset = CGSizeMake(-1, 1)
cell?.rQuizView.layer.shadowOpacity = 0.4
return cell!
} else {
var cell = tableView.dequeueReusableCellWithIdentifier("relatedTableViewCell") as? relatedTableViewCell
if cell == nil {
let nib:Array = NSBundle.mainBundle().loadNibNamed("relatedTableViewCell", owner: self, options: nil)
cell = nib[0] as? relatedTableViewCell
}
if indexPath.row == 0 {
let label = UILabel(frame: CGRectMake(22, 10, 200, 40))
label.font = label.font.fontWithSize(25)
label.textAlignment = NSTextAlignment.Center
label.text = "Featured Quiz"
cell!.addSubview(label)
}
else {
}
let info = featuredQuizArray.objectAtIndex(indexPath.row) as! relatedInfo
cell?.relatedImage.sd_setImageWithURL(NSURL(string: info.featuredqImage!))
cell?.relatedLabel.text = (String: info.featuredqName!)
cell?.rQuizView.layer.shadowOffset = CGSizeMake(-1, 1)
cell?.rQuizView.layer.shadowOpacity = 0.4
return cell!
}
Hope It will help.
I have a UITableView (multiple sections) with custom dynamic cells. Each cell has a UIStepper representing selected quantity.
In order to send the selected quantity (UIStepper.value) from the cell back to my UITableViewController, I have implemented the following protocol:
protocol UITableViewCellUpdateDelegate {
func cellDidChangeValue(cell: MenuItemTableViewCell)
}
And this is the IBAction in my custom cell where the UIStepper is hooked:
#IBAction func PressStepper(sender: UIStepper) {
quantity = Int(cellQuantityStepper.value)
cellQuantity.text = "\(quantity)"
self.delegate?.cellDidChangeValue(self)
}
And from within my UITableViewController I capture it via:
func cellDidChangeValue(cell: MenuItemTableViewCell) {
guard let indexPath = self.tableView.indexPathForCell(cell) else {
return
}
// Update data source - we have cell and its indexPath
let itemsPerSection = items.filter({ $0.category == self.categories[indexPath.section] })
let item = itemsPerSection[indexPath.row]
// get quantity from cell
item.quantity = cell.quantity
}
The above setup works well, for most cases. I can't figure out how to solve the following problem. Here's an example to illustrate:
I set a UIStepper.value of 3 for cell 1 - section 1.
I scroll down to the bottom of the table view so that cell 1 - section 1 is well out of view.
I set a UIStepper.value of 5 for cell 1 - section 4.
I scroll back up to the top of so that cell 1 - section 1 is back into view.
I increase UIStepper by 1. So quantity should have been 4. Instead, it's 6.
Debugging the whole thing shows that this line (in the delegate implementation of UITableViewController) gets the wrong quantity. It seems indexPathForCell is getting a wrong cell thus returning a wrong quantity?
// cell.quantity is wrong
item.quantity = cell.quantity
For completeness sake, here's cellForRowAtIndexPath implementation where the cells are being dequeued as they come into view:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "MenuItemTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! MenuItemTableViewCell
cell.delegate = self
// Match category (section) with items from data source
let itemsPerSection = items.filter({ $0.category == self.categories[indexPath.section] })
let item = itemsPerSection[indexPath.row]
// cell data
cell.cellTitle.text = item.name + " " + formatter.stringFromNumber(item.price)!
cell.cellDescription.text = item.description
cell.cellPrice.text = String(item.price)
if item.setZeroQuantity == true {
item.quantity = 0
cell.cellQuantityStepper.value = 0
cell.cellQuantity.text = String(item.quantity)
// reset for next time
item.setZeroQuantity = false
}
else {
cell.cellQuantity.text = String(item.quantity)
}
.....
....
..
.
}
You need to update the stepper value in the else clause in cellForRowAtIndexPath:
else {
cell.cellQuantity.text = String(item.quantity)
cell.cellQuantityStepper.value = Double(item.quantity)
}
Otherwise the stepper retains the value from the previous time the cell was used.