Changing the position of custom UIButton in custom UITableViewCell
Asked Answered
C

7

2

I need to change the position of UIButton dynamically. I do this in cellForRowAtIndexPath:. I alter the frame of the button in that method. The change is not displayed when the table is initially displayed. But when I scroll past the cells and come back to it, it gets displayed. Similarly, when I first scroll to cells not visible initially, there is no change. The change occurs when I scroll to it the second time.

I have tried setNeedsDisplay: on both the custom button and the table view. I have done this even in willDisplayCell:forRowAtIndexPath:. How do I solve this?

EDIT: I forgot to mention that I am loading the UITableViewCell from a nib file. Here is the code to resize the frame.

label1.text  = //text from some source
label1.text = [label1.text stringByAppendingString:@"  |"];
[label1 sizeToFit];
CGRect frameAfterResize=label1.frame;
float x=frameAfterResize.origin.x+frameAfterResize.size.width;
button.frame=CGRectMake(x+2, button.frame.origin.y, button.frame.size.width,button.frame.size.height);
Cuesta answered 2/12, 2011 at 6:24 Comment(12)
please provide your codeSaiva
@Saiva I don't have any code that interferes with the function. Logically the display should reflect whatever is altered in cellForRowAtIndexPath. Am I correct?Cuesta
Do you add button in the contentView? This week i got project to work with, and there used -heightForRowAtIndexPath tableview datasource(?) method to set width of controls in tableView's cells.Edouard
@RomanTemchenko : Yes, the button is present as a subview in the contentView. I don't see how changing the width will help meCuesta
@Saiva I have provided code.Cuesta
Width is a part of frame property. Change position in -heightForRowAtIndexPath. That's not good approach mb but i think it will work.Edouard
@RomanTemchenko It does not work in that method also. Although I got the cell for the tableview using cellForRowAtIndexPathCuesta
I guess, and I would bet money on that, that you placed your code in the wrong part. Could you paste your whole code from cellForRowAtIndexPath? Seems to me like you put the 'unmodified' version of your cell on the screen and modify it while moving it out of the screen. In addition to that, your coding style could use some advices!Psychological
@wegginho The cellForRowAtIndexPath code is way too big to put it up here. I guarantee that I am definitely putting the modified frame on screen first. I ve checked it with logs, breakpoints etc. I don't modify the cell in any other place. As for my coding style, I could definitely use your advise and I am all ears.Cuesta
Concerning encapsulation one method should never be bigger than one printable site. Think about creating an extra class for your cell. This is in some cases less performing but i guess some button movement should be calculated fast :)Psychological
not really related to the question, but I just wanted to let you know that instead of float x=frameAfterResize.origin.x+frameAfterResize.size.width;you can simply write float x=CGRectGetMaxX(frameAfterResize);Arnaldo
Please provide your entire cellForRowAtIndexPath method, or host it somewhere and link to it so I can see it. Frankly, everyone is guessing in the dark because you haven't shown us your full block of code--we have to assume you've done things right that you may not have.Stealing
H
6

Make your own UITableViewCell subclass. Create and add the button to the cell's contentView in your init method, or create and add it lazily via an accessor property. Override layoutSubviews and postion the button as desired.

Something like this:

@implementation MyCustomCell

- (void) init
{
    self = [super initWithStyle: UITableViewCellStyleDefault reuseIdentifier: nil];
    if ( self != nil )  
    {
         _myButton = [[UIButton buttonWithType: UIButtonTypeRoundedRect] retain];
         [self.contentView addSubview: _myButton];
    }

    return self;
}

- (void) layoutSubviews
{
    [super layoutSubviews];

    // dynamic layout logic:
    if ( ... )
    {

         _myButton.frame = CGRectMake( 10, 10, 100, 30 );
    }
    else 
   {
         _myButton.frame = CGRectMake( 20, 10, 50, 30 );

   }
}

Alternatively, you can attempt to do cell layout in - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath

Hereabouts answered 6/12, 2011 at 23:37 Comment(5)
I vote for this. You can even add a @property for myDynamicThingy that calls setsNeedLayout, and it will help you separate your display code from your view controller.Coiffure
A variation on this solved my problem (at #8373714). TomSwift, feel free to re-post this answer there, there's a bounty there as well.Protoactinium
@TomeSwift Thank you. Apologies for the late reply. I tweaked on this and made it work. I do have one more small problem with your answer. I am using this to load from nib- cell = (CustomTableViewCell *)[[[NSBundle mainBundle] loadNibNamed:@"nibname" owner:self options:nil]objectAtIndex:4]; This does not call the init method. How does it get initialized.Cuesta
The nib-loader calls initWithCoder:. You can overload that if you want.Hereabouts
@Hereabouts Please have a look at my post here. I've been struggling for a long time to fix this. Its very similar to this problem. Here is the link to it i hope you can help me would be very grateful #27348437Bulbil
O
3

I had the same issue in which I was loading cell from a nib, but none of the above solutions completely worked for me. They solved the problem for few scenarios but not all of them.

Here is the solution which worked for all scenarios.

@implementation MyChannelCell
-(void)drawRect:(CGRect)rect
{
    [super drawRect:rect];
    //Calling from here fixed issue for first time cell load
    [self alignCellViews];
}
-(void)layoutSubviews
{
    [super layoutSubviews];
    //Calling from here fixed issue for coming back to the tableview from other ViewConroller
    [self alignCellViews];

}
/**
Method to change the layout and positions of sub-views
*/
-(void)alignCellViews
{
    //Code to position sub-views
    //self.lblSubscribers is an outlet from nib of type UILabel
    [self.lblSubscribers setFrame:CGRectMake(140, 26, 36, 26)];

    [self setNeedsDisplay];
}

@end
Obit answered 23/6, 2014 at 13:32 Comment(0)
P
1

Have you tried reloading the table after changing the cell's frame by using

[self.tableView reloadData]

I think it should solve your problem.

Porphyroid answered 2/12, 2011 at 9:39 Comment(1)
Nope. It goes in an infinite loop and the tableview never gets displayed. I am just changing the frame of a button inside the cell.Cuesta
V
1
  1. Make your own UITableViewCell subclass
  2. Connect all outlets (button as well) from your xib to this class
  3. Make custom method in this UITableViewCell subclass for example - see below, cant format it there.
  4. in cellForRowAtIndexPath: use this method to change frame and redraw cell

    [cell setNewFrame:....];

it works fine for me

 -(void)setNewFrame:(CGRect)newFrame
    {
        myButton.frame=newFrame;
        [self setNeedsDisplay];
    }

if you want to adjust button to text

-(void)setNewCaption:(NSString)newCaption
{
  label1.text  = newCaption
  label1.text = [label1.text stringByAppendingString:@"  |"];
  // calculate new frame
  myButton.frame=//new frame
  [self setNeedsDiplay];
}
Vicegerent answered 12/12, 2011 at 2:27 Comment(2)
This is an interesting way. How do I set up the outlet to the UITableViewCell subclass. I am not able to find it in file's owner.Cuesta
in case of custom UITableViewCell you can see all outlets not in File Owner but in TableCellView. First choose your custom subclass in Inspector for this TableCellView view. Then you can see all outlets for them on appropriate tab in InspectorVicegerent
S
0

Call setNeedsDisplay with your UITableViewCell object.

[cell setNeedsDisplay];

in cellForRowAtIndexPath:

Scutt answered 2/12, 2011 at 6:31 Comment(1)
Does not work. This cell is displayed after it is loaded right? So should the display not reflect what is given in cellForRowAtIndexPath:?Cuesta
A
0

If it doesn't display as required initially, but does after scrolling, then my guess is that you are altering the label position only when a cell is dequeued and not when it is originally loaded from the nib.

You havent given this part of your cellForRowAtIndexPath method so I can't give any more specific advice at this point, but either the code above is not being executed, or label1 is nil when a new cell is made (ie when the dequeue method returns nil and you load from the nib).

Alternate answered 2/12, 2011 at 7:8 Comment(4)
I ve updated the code. I set the label1's text right before the sizeToFit. The code is getting executed and label1 is not nil when loaded from nib. I put up a breakpoint to check it. The text is dispayed fine btw.Cuesta
Have you logged the frame before and after sizeToFit? I dont think that method does what you want - you should use the NSString sizeWithFont method to obtains the width of your string.Alternate
Yes, I ve seen the frame before and after sizeToFit. The changes are made appropriately, so I should be able to use this method. I will look into sizeWithFont and see if that can be implemented.Cuesta
Even if I get the width with sizeWithFont, I will have to manually resize the label. Doesnt sizeToFit already do that for me?Cuesta
S
0

When drawing UITableViews, table cells are re-used so there is only one instance of a UITableViewCell identified by the string passed to the dequeueReusableCellWithIdentifier:.

If you update the cell in the cellForRowAtIndexPath: method it will not have the effect you expect - each time you will be referring to the same cell (so each cell will have the button in the same position). Hence the problem you are seeing that scrolling makes the button change position.

The easiest thing to fix your problem is alter the code to stop re-using cells by creating a new cell each time in cellForRowAtIndexPath. Update the code like this:

// comment out this line
// cell = [tableView dequeueReusableCellWithIdentifier:identifier];

NSArray *nib = [[NSBundle mainBundle] loadNibNamed:nibName];
cell = [nib objectAtIndex:0];

Please note though that re-using cells is preferred for efficiency reasons (to save memory and reduce memory allocations).

Your ideal solution is to create a custom sub-class of UITableViewCell and update the postion of the UIButton in the layoutSubviews method.

Sambo answered 6/12, 2011 at 23:25 Comment(1)
Thank you for the answer. I did end up using custom table cell as in tom swift's answer. The part where you tell me to re-use will not work at all because the frame changes only after it is reused. If it is not re-used, it will never change(I checked)Cuesta

© 2022 - 2024 — McMap. All rights reserved.