Automatically adding UI elements to the UIStackView while UITable Scrolling - ios

In my app have an UITable.In the table cell i have added UIStackView to populate while loading the table cell.
It was working fine till scroll down the table, The stack view get added more elements when I scrolling up and down. (elements means UIButtons, I will replace them with UIlabel in future)
I don't have any idea to solve this problem. Thank you..
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
UIStackView *stkItems=(UIStackView *)[cell viewWithTag:8];
for(int i=0;i<5;i++) {
UIButton *btn=[UIButton buttonWithType:UIButtonTypeSystem];
[btn setTitle:#"test btn" forState:UIControlStateNormal];
[stkItems addArrangedSubview:btn];
}
}

A table view reuses cells when they go off the screen. That's why you're sending a message named dequeueReusableCellWithIdentifier:. You never take buttons out of the stack view. Every time the cell is reused, you add five more buttons to the buttons that are already in the stack view.

I had a similar issue, adding arrangedSubviews in a tableview cell.
The trick here is, in the prepareForResue of your cell, remove each of your stackView's subviews from their superview with removeFromSuperview, AND, call the removeArrangedSubview.
Is should look like this :
for view in self.views {
radioView.removeFromSuperview()
}
self.views.removeAll()
for arrangedSubview in self.stackView.arrangedSubviews {
self.stackView.removeArrangedSubview(arrangedSubview)
}
As the Apple documentation says :
[The removeArrangedSubview] method does not remove the provided view
from the stack’s subviews array; therefore, the view is still
displayed as part of the view hierarchy.
Hope it will help someone ;)

From that code it seems that every time your app needs to draw a cell it will add buttons to the UIStackView, so when a cell is reused (by the dequeueReusableCellWithIdentifier) it still contains the buttons, but your code keeps adding more every time. Maybe you should check if the UIStackView has enough buttons or clear all buttons and add what you need.
Hope this helps

i think i solved it. But I don't know is it up to standard.
this is the updated.
i have initialized and declare it to 0 on up.
#implementation HistoryViewController
int data=0;
and change cellForRowAtIndexPath like this so it will not update same stackview again
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"%ld",indexPath.row);
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(data <=indexPath.row)
{
data=(int)indexPath.row;
UIStackView *stkItems=(UIStackView *)[cell viewWithTag:8];
[stkItems.subviews makeObjectsPerformSelector: #selector(removeFromSuperview)];
for(int i=0;i<5;i++) {
UIButton *btn=[UIButton buttonWithType:UIButtonTypeSystem];
[btn setTitle:#"test btn" forState:UIControlStateNormal];
[stkItems addArrangedSubview:btn];
}
}
return cell;
}

Since the cell is reusing the UIStackView you have to remove any subviews before you add new ones. To do this just call the following code before you loop through and add the arranged subviews to your UIStackView:
Swift 3
for view in stackView.arrangedSubviews {
view.removeFromSuperview()
}
Objective-C
for (UIView *view in stackView.arrangedSubviews) {
[view removeFromSuperview];
}
*Note: If you call [stackView removeArrangedSubview:view] according to Apple's comments it doesn't actually remove it completely from the receiver. See quote below:
- (void)removeArrangedSubview:(UIView *)view
"Removes a subview from the list of arranged subviews without removing it as
a subview of the receiver.
To remove the view as a subview, send it -removeFromSuperview as usual;
the relevant UIStackView will remove it from its arrangedSubviews list
automatically."

Related

Image on cells not all the way to the left

I am learning about UITableview on iOS and following a course online. I get the table showing fine, but the images on my cells are not all the way to the left (whereas the instructor's ones are). Here is a screenshot of the cells in question:
I don't want that gap, I want the images to be positioned right at the beggining of the cell, all the way to the left. I have done some research and it seems Apple has changed the default look of the cells between ios6 and ios7 so that now the images in cells show a little gap at the left. To get rid of it, I have tried UIEdgeInsets:
[tableView setSeparatorInset:UIEdgeInsetsZero];
and that's not working. I also have tried this approach:
cell.imageView.frame = CGRectMake( 0, 0, 50, 55 );
Nothing happens. So how would I go about it? Thanks
edit-------------------------------------------------------------------------------------------------------------------------------
Still not have found the answer to this. The solutions posted here don't work. I found this piece of code:
self.tableView.contentInset = UIEdgeInsetsMake(0, -50, 0, 0);
Which besides completely puzzling me (as the parameter affected should be the y?) I thought solved the issue by making the image on the cell appear all the way to the left, until I realised it only moved the whole view to the left (as I should have expected I guess) leaving an equal gap on the other side of the screen. All I want is for my images in the cells to appear all the way to the left of the cell as it used to be the case on previous ios. Thanks
It happens because default table content offset from left is 15, you should change it with 0.
See this once, you get idea Remove empty space before cells in UITableView
If you create custom cells. UITableViewCell have owner imageView. Change title of image in your cell.
If you use default cell, use custom cell with constraint Leading space = 0.
It is better not use default imageView of the cell. Drag and drop UIImageView from objective library, create a custom table view cell (Child class of UITableViewCell) then create and outlet of the image view just dragged.
The spacing in the UITableViewCell is because of the default TRUE returned by shouldIndentWhileEditingRowAtIndexPath method of UITableViewDelegate.
I was able to reproduce your problem by the below scenario:
UITableView is in editable mode:
self.tableView.editing = true
And you have implemented:
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewCellEditingStyleNone;
}
To correct your code:
If you do not want to set Editing Style then you can turn off the editing mode by
self.tableView.editing = false
and remove editingStyleForRowAtIndexPath.
Else if you need editing mode then set the appropiate Editing style(UITableViewCellEditingStyleDeleteor UITableViewCellEditingStyleInsert) or simply turn the indentation off.
- (BOOL)tableView:(UITableView *)tableView
shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath {
return FALSE;
}
You must create a custom cell, by adding a new class as a subclass of UITableViewCell. then you can design cell with autolayout and constraints which will resolve the issue.
there is a another concrete way to achieve this by creating subclass uitableviewcell (custom class).
steps to follow
create a class subclass of UITableViewCell.
in .h file create properties and outlets of UI components.
go to storyboard and add table view cell inside the tableview.
now add UI components like: imageview or button etc and set the x, y values according to.
make class of custom cell your className using identity inspector see image.
connect all outlets of UI components.
use below code uitableview
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *MyIdentifier = #"uniqueIdentifire";
yourCustomClassForCell *cell = (yourCustomClassForCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil){
cell = [[yourCustomClassForCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier];
}
cell.imageView.image = [imageAry objectAtIndex:indexPath.row];
}
Dont forget to give identifire by selecting your cell using storyboard Attribute inspector uniqueIdentifire to identifire property see image.
Also you can give some vertical space between cells by just to add this below code (Method only) inside customeCellClass.
- (void)setFrame:(CGRect)frame { // method to insert gap between table view cell
frame.origin.y += 6;
frame.size.height -= 2 * 6;
[super setFrame:frame];
}
You can not really change the frame of the inbuilt subviews of uitableviewcell like imageview, accessoryview. But if you create a custom tableviewcell class(even if you do not add any other subelement to it), you can change the frame of the inbuilt imageview by overriding the layoutSubviews method inside the UITableViewCell. I have tried it and it works.
#import "TableViewCell.h"
#implementation TableViewCell
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
-(void) layoutSubviews{
[super layoutSubviews];
CGRect frame = self.imageView.frame;
frame.origin.x = 0;
self.imageView.frame = frame;
}
#end

UITableviewCell subview appears only on scrolling or cell reuse (iOS)

As in the image below, the UITableView subview appears only on tableview reload or cell reuse (during scrolling, mostly). The blue color circle is what I want in my UITableViewCell. When it first appears, it will be a small dot as you can see in the picture, and on scrolling or refreshing the tableview, it appears as the full circle.
What can be the issue?
I use the following code in cellforRowAtIndexPath method
cell.categoryRoundBackground.layer.cornerRadius=cell.categoryRoundBackground.frame.size.height/2;
try using dequeueReusableCellWithIdentifier: forIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomTableViewCell *cell1 = (CustomTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"CustomID" forIndexPath:indexPath];
The most likely problem is that at the moment when you access your cell's frame height the first time by calling
cell.categoryRoundBackground.frame.size.height / 2
the cell has never been placed in a table view, it has no idea what its frame height is going to be, and so it uses some default value. The actual height depends on the value returned by your code in heightForRowAtIndexPath: method.
You can work around this problem by computing the frame size yourself. You should be able to do that, because your code supplies the value to heightForRowAtIndexPath:.
It's possible that you change the corner radius before that the view layouts its subviews.
You should try to put the line
cell.categoryRoundBackground.layer.cornerRadius=cell.categoryRoundBackground.frame.size.height/2;
inside
- (void)viewDidLayoutSubviews {}
You have to Override the method in CustomTableViewCell
- (void)layoutSubviews{
[super layoutSubviews];
self.categoryRoundBackground.layer.cornerRadius=self.categoryRoundBackground.frame.size.height/2;
self.categoryRoundBackground.layer.masksToBounds = YES;
}
and In CellForRowAtIndexPath: you have to write these lines at the end
// Update layout
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
Hope it will solve your problem

cellForRowAtIndexPath method custom button show 2 time

I used this code in cellForRowAtIndexPath method. When I click on button or scroll table this button is shown two times. Why is this button shown 2 times please help me?
UIButton *trashbtn=[[UIButton alloc]initWithFrame:CGRectMake(cell.frame.size.width-20, cell.frame.size.height-30, 20, 20)];
[trashbtn setImage:[UIImage imageNamed:#"editor_trash"] forState:UIControlStateNormal];
[trashbtn addTarget:self action:#selector(DeleteMyAssociate:) forControlEvents:UIControlEventTouchUpInside];
[trashbtn setTag:indexPath.row];
[cell addSubview:trashbtn];
You should firstly add a UITableViewCell within your table in IB. Then give an identifier e.g. "MyCellIdentifier" to that cell. Still on IB, add your outlets, to that cell e.g. the button, the textFields... You can initially set the button to be invisible. Then in the method cellForRowAtIndexPath, you do:
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyCellIdentifier"];
if(cell){
myButton.hidden = NO; //
myTextField.text = #"BlaBlaBla";
}
}
Hey I can give you a suggestion that use auto layout and storyboard to add button rather than adding it programmatically. That is more better and cleaner approach.
The problem you are facing is that cells are reused in tableviews, So the first time you create a button and add it to the cell, it appears once. But if it is dequeued, it already has the button, so when you add another one you end up with multiple buttons.
There are two ways to correct this.
Firstly, remove the button in the cell's prepareForReuse method, which is called just after the cell is reused.
Secondly, avoid creating custom views in the cellForTableView... method. Use a custom cell that already has the button. Now, you may ask, how do I hook up the action for the button if I do it this way? You can either provide a delegate method for your cell that calls back to your view controller, or you can pass your cell a block to perform when the button is clicked.
as far as i can get u already have a custom cell.. add a button to existing cell and create an IbOutlet for the same in customCell.h file
then in cellForRowAtIndexPath method access the button using its IBOutlet
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
customCell *cell =(customCell*) [tableView dequeueReusableCellWithIdentifier:#"customCell" forIndexPath:indexPath];
[cell.btn setTitle:#"blabla" forState:UIControlStateNormal];
[cell.btn setTag:indexPath.row];
[cell.btn addTarget:self action:#selector(method:) forControlEvents:UIControlEventTouchUpInside];
if(condition)
{
cell.btn.hidden=YES;
}
else
{
cell.btn.hidden = NO;
}
return cell;
}
Do not forget to register for the customCell class in your ViewController class
[tableName registerNib:[UINib nibWithNibName:#"customCell" bundle:nil] forCellReuseIdentifier:#"customCell"];
you can hide or unhide the button as per your requirements as well as add different actions to the button in each class.
Happy Coding..!!
Do vote it if my code was helpful for you.. ;)

How can I make a Custom UITableViewCell top cell draw uniquely after reuse?

So I've made some custom table view cells and they draw correctly and look great, however once I scroll past the edge of the visible cells they start being reused, which is fine, except that when I scroll back the reused cells are still shown and don't redraw. Specifically all the cells look the same except for the top-most cell.
Pictures detailing the occurrence:
How I have this coded up, is when the cells get made if the indexPath.row is greater than 0 add an "overlap effect" which is just a gradient on a uiview placed underneath the custom drawing on the UITableViewCell's contentView.
This is how I add the overlap effect in the UITableViewController's tableView:cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"APostCell";
PostCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
CustomPost *aPost = [self.posts objectAtIndex:indexPath.row];
if (indexPath.row > 0) {
[cell addOverlap];
}
cell.postDateLabel.text = [aPost datePostedAsString];
return cell;
}
How would I implement this [cell removeOverlap]?
Try this
if (indexPath.row == 0) {
//remove overlap here
} else {
[cell addOverlap];
}
beacuse, except 1st cell all have overlap.On scrolling the reused cell have the overlap. So for first cell remove the overlap if present.
So after I posted the question I figured it out and, since I had the question and had previously not found any information on the subject figured I would share.
So whenever
PostCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]
is called, the table view either creates a new cell or reuses an old one. When a new cell is created and it is not the top cell (indexPath.row == 0) it adds the overlap to the UITableViewCell. And if it reuses the cell, that method still gets called, regardless what cell is being reused. So naturally once the cell created at the top is reused, the gradient view still gets added to cell.contentView and it stays there even when I'm reusing for the topmost cell again.
In fact adding the overlap view in this way will stack multiple overlap views into the same cell.
So what has to be done (if you intend to customize the cell appearance this way) is to remove the added views before each reuse of the cell. So you have to overwrite the custom tableviewcell's prepareForReuse method and do just that like so.
- (void) prepareForReuse {
[super prepareForReuse];
[self removeOverlap];
}
Be SURE the cell has the overlap view otherwise your app will break by trying to remove views not there. so have something like
- (void) removeOverlap {
if ([self.contentView.subviews count] > 1) {
//This method works based on the assumption [cell addOverlap] adds new view
//underneath existing views - like [self.contentView insertSubview:overlappedView atIndex:0];
[[self.contentView.subviews objectAtIndex:0] removeFromSuperview];
}
}

On iOS, what is the difference between adding a subview to a UITableViewCell object "cell" vs to "cell.contentView"?

In the following code, if we do [cell addSubview: someLabel] vs [cell.contentView addSubview: someLabel], they seem to work the same. Is there any difference doing one or the other? (the custom cell in the real code is adding UIImageView and UILabel) (UIView, on the other hand, doesn't have contentView, so we don't need to add subview to its contentView. UITableViewCell is a subclass of UIView by the way)
-(UITableViewCell *) tableView:(UITableView *) tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
if ([tableView isEqual:self.songsTableView]){
static NSString *TableViewCellIdentifier = #"MyCells";
cell = [tableView dequeueReusableCellWithIdentifier:TableViewCellIdentifier];
if (cell == nil){
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:TableViewCellIdentifier];
}
// ... some code to create a UILabel (not shown here)
[cell addSubview: someLabel]; // vs using [cell.contentView addSubView: ...]
I believe If I am not wrong, the contentView is a subview of UITableViewCell.
If you look at this page here, you can see there are actually 3 subviews in a UITableViewCell
I think by default, the Editing Control is hidden until you enter edit mode for a table in which case, the Editing Control appears (the minus button left of each row) and your contentView gets resized and pushed to the right. This is probably what gives the "proper animation" effect mentioned by the other answer.
To test the difference, try adding a subview such as UILabel with text, to the cell rather than the cell.contentView. When you add it to cell rather than cell.contentView and you enter edit mode for your table, I believe your UILabel will not resize, you will see the edit button ontop/below the minus sign button.
Placing your views in the contentView affects proper animation in and out of edit mode. Place all of your subviews in contentView when you're not subclassing, which should be all of the time unless you know what you're doing.

Resources