How to mix static and dynamic tables in a single view - ios

What is the approach taken to incorporate table view with dynamic content as well as static table cells, like the following views (Any sample code is appreciated but not necessary). For the sake of learning, I would like to hear how you would go about constructing each of the following screens:

As far as possible, you shouldn't use static cells and dynamic cells together. It's a bad practice. This is because, when you use static cells, the dataSource for the tableView is set to be static, and cannot change at all.
But, there is a hack to this. And, you can use both static and dynamic cells together. But, you have to be really careful at doing this, otherwise your app can crash.
ToDo List:
1. Override the
cellForRowAtIndexPath method.
func tableView(_ tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// For static section i.e. 0 index
if indexPath.section == 0 {
// This returns the static cells
return super.tableView(tableView, cellForRowAtIndexPath: indexPath)
} else {
// This gives your dynamic cell
let cell = myDynamicCell()
return myDynamicCell
}
}
Here, the myDynamicCell() method is your own method to create your dynamic cells. You cannot use prototype cell at this point, because you chose static cells already. So, you have to create the cells through code.
Note:
Keep in mind you are messing with the static dataSource. If any errors come at this point, it is your own responsibility as Apple states.
Also, this process is really dangerous, try to use dynamic cells and then set the first section dynamically to static. And, rest would be easy. This is the best approach to take. I would recommend this approach, but above approach would still work for this time, at least at the time of creating new cells.

Related

Why UITableViewCell is not accessible (for VoiceOver)

I'm not trying to solve any problem. Of course you can set isAccessibilityEnabled = true and it works. My question is: why is it turned off by default and there is no appropriate section in the interface builder. It seems for me like it's not recommended to make UITableViewCell subclasses accessible. Is there any better ways to make cells accessible? I mean to make a cell as one accessibility element which will contain all the info for VoiceOver.
why is it turned off by default
A UITableViewCell may be seen as a container inside which many elements are embedded (buttons...) and, as is, you can't have simultaneously a parent view (the table view cell) and its child views (label, button...) that are both accessible with VoiceOver: either your cell can be selected or its content.
By default, the content must be seen by VoiceOver: add two buttons in your cell and you'll see the difference by enabling/disabling the accessibility of the cell.
Is there any better ways to make cells accessible? I mean to make a cell as one accessibility element which will contain all the info for VoiceOver.
To reach your goal, the best way is to make your cell accessible while providing an accessibilityLabel and adding custom actions if many actions are planned in this cell (with buttons for instance).
Following this rationale improves the user experience: one unique selection with possible actions.
If you don't want the elements inside each cell to be read out, just define each one of your cells as follows:
override func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier",
for: indexPath)
cell.isAccessibilityElement = true
cell.accessibilityLabel = "APPEND YOUR LABELS HERE"
// Add everything you need to construct your cell here.
return cell
}
That's the simplest configuration but you may decide to reach every elements in the cell and, in this case, it's quite different: see this answer or this one if you need some implementation examples for this.
Personnally, I always subclass the table view cell to define its trait, its array of accessiblity elements and its potential actions so as to control the way information are provided to the VoiceOver user: I find it very flexible even if it seems to be tedious at first sight. ;o)

Reusing cells with dynamic layout content at runtime

I have a type of UITableViewCell that lets the user add/remove as many UITextViews as they want at run time.
I'm running into issues when trying to reuse/dequeue cells of that type, as sometimes the tableview cells just start overlapping when you scroll up and down. When I dequeue/return the cell, I'm running a setup method (which initiates a teardown method internally first to remove all the previous views), and uses the model to setup/restore all the necessary views and layout constraints.
if let cell = tableView.dequeueReusableCell(withIdentifier: "MultipleContentCell", for: indexPath) as? MultipleChoiceTableViewCell {
cell.setupCellWithModel(model: model)
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
cell.delegate = self
return cell
}
I can't really figure out why that cells sometimes overlap in the tableview, but I'm guessing it has to do with the layout being recreated on the fly. I'm considering not reusing these types of cells and just storing them in a list.
My question is: are reusable cells always suppose to have the same general UIView layout, and only the content changes? Am I not supposed to use reuse these types of cells? Or has someone experienced this before?
Thanks
The UITextView are created each time you dequeue cell and never delete. To repair that use function prepareForReuse(). You have to define, what your cell should do before dequeue in MultipleChoiceTableViewCell. For example:
override func prepareForReuse() {
super.prepareForReuse()
for view in speciesName.subviews {
if view is UITableView {
view.removeFromSuperview()
}
}
}
I added similar question few days ago:
Cells in UITableView overlapping. Many cells in one place
If you have some question, I can try to help you more tomorrow.
Cheers!
In general, yes. You want the physical layout of your cells to be static, and only vary the contents when you recycle them. If you add views to your cells in cellForRow(at:) then the burden is on you to manage the extra fields to avoid duplicate views.
Your case where you add a variable number of views to a table view cell based on user interaction is an odd case where you might need to add and remove cells on the fly.
One way to handle this would be to put all of your text fields in a container view, add an outlet to that container view, and then simply use code like this in your prepareForReuse or cellForRowAt function:
containerView.subviews.forEach { $0.removeFromSuperview() }

Setup Header Cell UICollectionView

I created collectionView as below:
I would like to create a collection view as same as this layout, with one biggest header cell like the one with red iPhone7. Now I wonder which approach is better, to create an extra cell or handle this in UICollectionViewFlowLayout?. My data is an array which fetched from JSON, so i would like to make the big cell is the first item.
Honestly, I see that second approach is quite complicated.
So can anyone help me to find best solution for this.
Thank you so much for reading this and have a nice day ahead.
Go to attribute inspector of UIColectionView.
From Accessories select Section Header
Once header view is visible , add your views to it.
Then in following method tweak accordingly:
override func collectionView(_ collectionView: UICollectionView ,viewForSupplementaryElementOfKind kind: String,at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionElementKindSectionHeader{
return headerView
}
}

Rows With Different UI Swift and also reusing any of cell in different viewcontroller to avoid repeated coding

I am making a layout similar to this
Here all cells are different UI (approx 9 cells). So I tried using XIB files for each one and added in
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
}
But using xib i have achieved UI but i am finding it complicated.
Please guide me how can i achieve this layout, if there is any better solution than XIB.
And if any of this cell has to be reused in Other ViewController, how to design and code it to avoid repeating.
What you could do is make different cells and then when you call the cellForRowAtIndexPath you can load in a different cell depending on the index you are at. For example, at index 3 you may want to load in cell A while at index 5-7 you may want cell C.
Just go to the tableview drop in the cells you need, design each one, link it with a class, and then set its reuse identifier. Don't add them as section headers but as prototype cells.

Should I use dequeReusableCellWithIdentifier?

I know this question is dumb. But suddenly got stuck up with this question.
When I use dequeReusableCellWithIdentifier, the cells are reused. To be more specific, first 'n' set of cells is reused - along with their references
As the same references are reused, I can't actually store local variables to the cells. I actually need to assign them everytime in cellForRowAtIndexPath
Assume I'm using a custom complex UITableviewcell. (I know we should reduce complexity. But still...)
Some views are to be added to the cell.
Best example i could think of is an image slider.
So the number of images in the image slider differs based on the datasource. So i need to add the views in cellForRowAtIndexPath. Can't avoid it.
Since the same reference of cells are reused, I need to add these views everytime cellForRowAtIndexPath is called. I think that is a bit of heavy load.
I thought of using drawRect method, but it does not work with UITableViewAutomaticDimension
If I don't use dequeReusableCell, I will have individual referenced cells, but it will cost the memory and performance.
So what is the best solution?
Can I use dequeReusableCell and still need not rewrite its content?
Edit 1:
By mentioning dequeReusableCell, I did mention dequeueReusableCellWithIdentifier - forIndexPath. I'm sorry for the confusion.
Edit 2:
I feel, I'm not so clear in my question.
Lets consider I have a array in my viewcontroller.
I'm configuring my cell in cellForRowAtIndexPath with dequeueReusableCellWithIdentifier - forIndexPath
So what happens is, everytime i scroll, when the invisible rows become visible, cellForRowAtIndexPath is called.
Lets say I have a image slider with n imageviews. For each cell, this 'n' differs. So I'm forced to draw the cell's view based on its dataSource.
So everytime the tableView calls cellForRowAtIndexPath, the cell is configured again and again.
I want to avoid this to improve performance.
what I do in this case is the following:
I always use dequeReusableCell:, for reasons you already said
I write a cleanCell method in the custom UITableViewCell class in order to clean everything has been already set on the cell (just to make it ready for reuse)
then in the cellForRowAtIndexPath: I configure my cell as desired
That's what I would do: I would move the logic of adding those heavy views inside the cell's class itself. So that in cellForRowAtIndexPath I would just set an object that has to be displayed inside the cell, and cell would create all the necessary views on its own (it's a view layer, after all). So inside cellForRowAtIndexPath it would look something like this:
cell = [tableView dequeueReusableCellWithIdentifier...forIndexPath...];
cell.modelObject = myModelObject;
and that's it.
Then inside the cell class I would implement optimizations for switching between different model objects. Like, you could defer releasing the heavy views, and reuse them, when the cell is reused.
To be honest, I don't get your example with the slider, but let's suppose you have a star rating with 5 stars, which can be visible or not. There are various way to do it, but let's assume you're just adding / removing a UIImageView for each star. The way you do it now, you would have an empty cell and create / add views in cellForRow. What I'm suggesting is to have 5 UIImageViews as part of your cell, and inside the cell set their visibility based on the modelObject.rating. Something like that. I hope, it helps to illustrate my point :)
UPDATE: when your cell can have an arbitary number of images inside of it, I would probably create them inside the cell's class. So, for instance, for the first model object we need 3 image views. So we create them. But then we don't release them in prepareForReuse, we wait for the next model object to come. And if it has, say, 1 image, we release 2 image views (or we don't, so that we didn't have to recreate them later, it depends on what is more critical: performance, or memory usage), and if it needs 5, we create two more. And if it needs 3, we're all set already.
In your case you should use dequeueReusableCellWithIdentifier(_ identifier: String, forIndexPath indexPath: NSIndexPath) method. This method automatically decides whether the cell needs to be created or dequeued.
I need to add these views everytime cellForRowAtIndexPath is called. I think that is a bit of heavy load.
You don't need to add those views every time cellForRowAtIndexPath gets called. Those views should be already added as a cell is instantiated. In cellForRowAtIndexPath you only assign images and other values to those views.
In case if you use storyboards and prototype cells your code should look like this:
var items = [UIImage]()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ImageCell", forIndexPath: indexPath)
let item = items[indexPath.row]
let recipient = item.recipients.first
// I have given my UIImageView tag = 101 to easily access it from the view controller.
// This allows me to avoid defining a custom UITableViewCell subclass for simple cells.
if let imageView = cell.viewWithTag(101) as? UIImageView {
imageView.image = item
}
return cell
}
1)I think you need to use "lazy load" of cells.
http://www.theappguruz.com/blog/ios-lazy-loading-images (for example)
2) You can create a property in your viewController, and create an NSMutableDictionary property, which will store the some data by the some key,
if your model have things like "ID" of the cells or you can use indexPath.row for it, then you can load it only one time, and next time when -cellForRow will be called, you can retrieve data from your dictionary by the
[self.dataDictionary objectForKey:#"cellID"];
And with this you, solve your repeating load.
In -cellForRow you can set the check,like.
if ([self.dataDictionary objectForKey:#"cellID"]) {
cell.cellImage = [self.dataDictionary objectForKey:#"cellID"];
} else {
UIImage *img = [uiimage load_image];
cell.cellImage = img;
[self.dataDictionary setObject:img forKey:#"cellID"];
}
And so on. Its like example, the dataType's you can choose by yourself, it's example.
Thanks for everyone helping me out. I think the better answer was from #Eiko.
When you use reusable cells, it must be completely reusable. If you are going to change its views based on the data, you either have to use a new identifier or avoid using reusable cells.
But avoiding reusable cells, will take a load. So you should either use different identifiers for similar cells or as #FreeNickName told - you can use an intermediate configuration and alter it on your need.

Resources