I have a table view with 3 types of custom cells.
Interface in storyboard.
3 different classes in the project for the cells.
I'm doing this currently and the cells IBOutlets are coming out to be nil.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = ListCell()
cell.configureCell(Data[indexPath.row])
return cell
}
class ListCell : UITableViewCell {
#IBOutlet weak var selectButton: UIButton!
func configureCell(data: ParamList) {
selectButton.setTitle("Select " + data.name, forState: .Normal)
}
#IBAction func selectButtonPressed(sender: AnyObject) {
print(selectButton.currentTitle)
}
}
I don't want to reuse any cells because every cell has different properties.
Some have textboxes other have different interactive matter which needs to be submitted in the end to a webservice.
I want to create new cells in memory for every cell.
Max cells will be around 15. So won't have much of a memory problems.
Any way I can Do that??
I did finally solve this.
Here's what I did for the same.
I am describing my process if anyone else wanna do the same.
Warning, only do this if absolutely necessary and you do not have that many cells that can affect the performance.
For making UITableView's cells to remain in memory even if scrolled outside the screen encapsulate your table view in a scroll view, and set your UITableView's scrollEnabled to false.
If using Storyboards set a height constraint to your TableView and make an IBOutlet to your code file.
Programatically add new cells with different identifiers.
"\(indexPath.section)\(indexPath.row)"
After getting your data, set your tableView's height to a multiplier of your data count.
i.e.
tableViewHeightConstraint.constant = heightOfOneCell * data.count
tableView.delegate = self
tableView.dataSource = self
This way you can scroll with the scrollView and all your cell will still be in the memory.
Set nil inside the method dequeueReusableCellWithIdentifier. So you can get what number of cells you want in memory.
Related
In my tableview, every cell will be different and determined by a JSON response from server. And there will be infinite possibilities. So defining a prototype for each type of cell is not possible.
For example, one cell will have labels and buttons, another cell have images and buttons in different orders.
How to achieve this dynamic structure in tableview cells?
Currently what I am doing is: adding views as subview in cellForRowAtIndexPath but scrolling is very laggy this way.
How to achieve this without affecting performance this much
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! MyCell
for myview in data[indexPath.row].myviews{
cell.addSubview(myview)
}
return cell
}
If you're using a table view then your content is going to scroll vertically, right?
There is a physical limit to the amount of UI that you can put horizontally. Limited by the screen size.
So I'm guessing your UI parts are being laid out vertically in the cell?
So instead of laying out a button, label, image, another button, and a text field vertically in a cell...
Create a cell type called ButtonCell, LabelCell, ImageCell, MultiLineLabelCell, TextFieldCell, etc...
So now, instead of creating one cell with all these elements added. You instead create multiple cells each containing one type of UI. Now you can dequeue your cells in any particular order (driven by your JSON) and won't lose the performance.
The only solution I see is to have empty cell and add/remove subviews as needed. But you should add new subviews to a cell only if you did not add them before.
For example:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! MyCell
if cell.contentView.viewWithTag(1) == nil {
let label = UILabel()
label.tag = 1
cell.contentView.addSubview(label)
}
let label = cell.contentView.viewWithTag(1)
// label config there
return cell
}
Also don't forget to add subviews to cell's contentView not to cell itself.
I have multiple tableviews in my ViewController and each tableview has different number of rows, please see image below:
Is there any way to match the height of the tableview according to its number of rows?
In your view controller, put code similar to the following:
override func updateViewConstraints() {
super.updateViewConstraints()
tableViewHeightConstraint.constant = tableView.contentSize.height;
}
Where tableViewHeightConstraint is an #IBOutlet to NSLayoutConstraint. Obviously with multiple table views you will need to reference multiple height constraints, so you'd just have one line in updateViewConstraints for each table view you want.
The advantage of this technique is that it takes all of the table view's content into account. It handles grouping, automatic cell height, etc.
You have to determine how many cells you have, then override
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return CGRectGetHeight(self.tableView.bounds) / self.tableView.numberOfRowsInSection(indexPath.section)
}
Btw, I suppose making use of header cells would be a better idea than implementing multiple tableviews.
I'm passing data to a custom UITableViewCell and based on that data I want to show or hide a dynamically added subview. I'm reusing a cell which is created in the storyboard. Everything is working as expected, until some of the cells are reused, for example while scrolling it will "randomly" hide or show the dynamic added subview.
What I need
I need a way to set the data of a cell through a method (setData), adding a dynamically created subview, while allowing a cell to be reused without creating glitches in its appearance, in particular the added subview as well the cells state.
Problem
I don't know where I should create the subview, so it doesn't have to be recreated when the cell is reused and so it won't bug when I want to hide or show it in the setData method. As well having access to the IBOutlet storyboardLabel while creating the new subview.
CustomTableViewCell
class CustomTableViewCell : UITableViewCell {
var data: DataItem?
var customSubview: UIView?
#IBOutlet weak var storyboardLabel: UILabel!
//setting the data of a cell and adding the subview
func setData(DataItem data) {
// adding the view
let customSubview = UIView.init(...)
customSubview.bounds = storyboardLabel.bounds
customSubview.hidden = data.showSubview
self.contentView.addSubview(customSubview)
}
}
Adding the cell
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("storyboardTableCell") as! CustomTableViewCell
cell.setData(self.data[indexPath.section][indexPath.row] as! DataItem)
return cell
}
Everything is working as expected, until some of the cells are reused...
Nothing is working as expected: you are adding object to a cached UITableViewCell, and of course, when you are passed that cached cell again on a subsequent cellForRowAtIndexPath, you are adding new objects.
Consider using a collection of views in IB, each satisfying your cell organization, thus saving adding subviews programmatically.
You need to clean up your cell.
Either remove everything you have added in your cellForRowAtIndexPath
You can use tags and viewWithTag if you want to refrain from re-adding already existing views
Or implement prepareForReuse()
You can find an example (and a similar discussion) on this Stack Overflow post.
I have looked around for this issue, and have not found an answer that works for my case. Basically, I have a tableView where each cell contains a collectionView. What I would like to do is to refresh the specific TableViewCell where the collectionView was scrolled, in order to update a label under the CollectionView, within that TableViewCell.
So when the user scrolls on the collectionView, I need to know in which cell that collectionView is. I have tried using didSelectRowAtIndexPath, however, it only is called when the non-CollectionView part of the cell is tapped. When the collectionView is tapped or scrolled, it isn't called. I am coding in Swift.
Any ideas on how I can do this?
Thanks!
This seems like a architecture issue. Sure it can be done the way you want, but it'd be much easier if your rearranged some things. There is a fundamental problem how you want to do this. You want to manage all of the cells and their collection views directly from your view controller. But this poses the problem of needing to know where messages are coming from and directing messages back to the correct cells and collection views. This will create a lot of bloat that can be fixed with a simple UITabelViewCell subclass. It also is a step in contracting Massive View Controller syndrome. Instead, you should make the individual cells responsible for managing their own collection views.
First off, make the UITableViewCell own and be the delegate and data source of the UICollectionView. This centralizes the data and more closely models the tree of data that you actually see on screen.
class CollectionTableViewCell: UITableViewCell {
#IBOutlet var collectionView: UICollectionView! {
didSet(newCollectionView) {
newCollectionView.delegate = self;
newCollectionView.dataSource = self;
}
}
var model: NSArray?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
extension CollectionTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return model?.count ?? 0
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath)
// configure cell
return cell
}
}
Now you wanted to have a state refresh when your collection view scrolls. So we're going to add a label to the cell in the prototype cell (Nib or Storyboard.)
#IBOutlet var indicatorLabel: UILabel!
And you want to update it when the collection view is scrolled. We can do that with the scrollViewDidScroll method off the UIScrollViewDelegate protocol. Because UICollectionViewDelegate implements the UIScrollViewDelegate, it's available for us to use since we implement the UICollectionViewDelegate in our extension.
// in the CollectionTableViewCell extension...
func scrollViewDidScroll(scrollView: UIScrollView) {
let scrollViewOffset = scrollView.contentOffset.x
let scrollViewWidth = CGRectGetWidth(scrollView.frame)
let completionString = String(format: "%# / %#", scrollViewOffset, scrollViewWidth)
self.indicatorLabel.text = completionString
}
So by making the individual cells responsible for their own respected collection views, we make managing them easier. It allows us organize our code to be more compact, understandable, and keeps us from getting Massive View Controller syndrome. The more code you can move out of your view controller, the better.
Some good talks to hear on this would be:
WWDC 2014 – Advanced iOS Application Architecture and Patterns
Let's Play: Refactor the Mega Controller!
You can use tag property of UITableViewCell. Set tag to row number and when cell is tapped, fetch tag property to find out the tapped cell index.
Your CollectionView will be contained within some kind of cell. Once you have found this cell, you can ask the table for the index. Navigate from your CollectionView up the view hierarchy to find the cell. For example:
CollectionView* collectionView = // your collectionView;
UITableViewCell* cell = (UITableViewCell*)[collectionView superview];
UITableView* table = (UITableView *)[cell superview];
NSIndexPath* pathOfTheCell = [table indexPathForCell:cell];
NSInteger rowOfTheCell = [pathOfTheCell row];
NSLog(#"rowofthecell %d", rowOfTheCell);
I'm having trouble getting a custom UITableViewCell to size properly when an Accessory is set. Why does the Accessory blow the cell sizing up on the initial display? When I scroll, with the accessory set, the sizing corrects itself - however, the initial view is not sizing correctly.
I've watched the WWDC14 (what's new in tableview) video several times and I've read many stackoverflow questions and tried many solutions. I think I have most of the problem solved - cells do resize for dynamic text - but I'm stumped on this strange initial behavior. I am running XCode 6.1.1 deploying to iOS 8.1.
I'm using a storyboard. I have a UITableViewController and custom UITableViewCell. I define constraints in the storyboard and there are no constraint warnings and I see no constraint messages in the console at runtime.
In my UITableViewController viewDidLoad()
tableView.estimatedRowHeight = 44
tableView.rowHeight = UITableViewAutomaticDimension
with override
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TableViewCell", forIndexPath: indexPath) as TableViewCell
cell.configure(titles[indexPath.row])
return cell
}
This is my entire custom UITableViewCell
class TableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
func configure(title: NSString) {
titleLabel.text = title
titleLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel.preferredMaxLayoutWidth = frame.width
}
}
This is what it looks like when Accessory None is set on the cell
This is what it looks like when I set Accessory Disclosure Indicator on the cell
Again, when I scroll with the accessory set the sizing corrects itself. I've tried adding a vertical constraint to the cell both in storyboard and also by adding all constraints programmatically with no success. Thank you for any thoughts.
I've seen this problem too with storyboard designed self-sizing cells. Try adding self.layoutIfNeeded() to an override of didMoveToSuperview in your cell class. I don't know if this is a bug, or we're just missing something that we're supposed to be doing.