UIImageView in custom UIITableViewCell not toggling on reload - iphone

I have a custom UITableViewCell. I've got a UIImageView on top of a UIButton. Now when I click the UIButton, I'm able to toggle the image of the UIImageView something similar to a radio button.
The problem is, when I toggle it to the state 'YES', there is a call to the server that makes the required change. Now when I reload the table, the UIImageView doesnt change to the 'YES' state. I set the UIButton state to 'Selected' and that's working fine. But the UIImageView doesnt change to the new image.
On update I get this:
When I actually want this:
The default state is this:
The updated state and the default state of the UIImageView is the same.
Here the round icon is the UIImageView and the rest is a UIButton
This is what I've been trying to do:
In the custom UITableViewCell:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
self.karmaLogo = [[UIImageView alloc] initWithFrame:CGRectMake(self.karmaButton.frame.size.width - 40, 8, 30, 30)];
[self.karmaLogo setImage:[UIImage imageNamed:#"logo-grey"]];
self.karmaLogo.userInteractionEnabled = NO;
[self.karmaButton addSubview:self.karmaLogo];
}
And in the UITableView delegate:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Creates the required custom cell
if (karmaCheck == 1) // Received from the server
{
cell.karmaButton.highlighted = YES;
cell.karmaButton.selected = YES;
cell.karmaLogo.image = nil;
[cell.karmaLogo setImage:[UIImage imageNamed:#"logo-black"]];
[cell.karmaLogo setNeedsDisplay];
[cell bringSubviewToFront:cell.karmaLogo];
}
else
{
cell.karmaButton.highlighted = NO;
cell.karmaButton.selected = NO;
cell.karmaLogo.image = nil;
[cell.karmaLogo setImage:[UIImage imageNamed:#"logo-grey"]];
[cell.karmaLogo setNeedsDisplay];
[cell bringSubviewToFront:cell.karmaLogo];
}
The execution does enter both the if and the else block as and when required but doesnt change the image. But the highlighting and the selection of the karmaButton (UIButton) works fine.

As per you statement "when I reload the table, the UIImageView doesnt change to the 'YES' state."
This is because whenever you reload the Table your - (void)setSelected:(BOOL)selected animated:(BOOL)animated gets called in which you are explicitly setting the image in ImageView using [self.karmaLogo setImage:[UIImage imageNamed:#"logo-grey"]];
So do your conditional checking in - (void)setSelected:(BOOL)selected animated:(BOOL)animated instead of cellForRowAtIndexPath:(NSIndexPath *)indexPath

You're adding the image as a subview of the button, so the image is getting highlighted when you highlight the button. The reason you're seeing a problem, I think, is due to a bug in UIImageView when you don't explicitly set the highlighted image. So, rather than toggling the image, try doing this outside of your if-else block:
[cell.karmaLogo setImage:[UIImage imageNamed:#"logo-grey"]];
[cell.karmaLogo setHighlightedImage:[UIImage imageNamed:#"logo-black"]];

Related

How do I add a UIActivity Indicator to every Cell and maintain control of each individual indicator

I'm trying to add an activity indicator to certain cells in my UITableView. I do this successfully in the method didSelectRowAtIndexpath using
CGRect CellFrame = CGRectMake(260, 10, 20, 20);
actindicator = [[UIActivityIndicatorView alloc]initWithFrame:CellFrame];
[actindicator setHidesWhenStopped:NO];
[actindicator setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleGray];
actindicator.tag =1;
[[cell contentView] addSubview:actindicator];
The catch is I need to control these multiple activity indicators from ANOTHER METHOD. I figured a property was a way to do this however by initialising a new instance of actIndicator every time, I loose reference's to all but the 'latest' init of the activity indicator thus meaning I can only control one.
What do i need to do here (if even possible?) to maintain reference to all the actIndicators so i can begin animating ALL of them?
Or Can I somehow use the actindicator.tag to control some form of reference.
Many thanks for any help.
EDIT: (Derived from answer) to access all Instances of Activity indicator with a tag of 1 in tableView (visible cells only) can use below from another method:
for (UITableViewCell *cell in [self.tableView visibleCells]) {
UIActivityIndicatorView *actView = (UIActivityIndicatorView *)[cell.contentView
viewWithTag:1];
[actView startAnimating];
activityFlag = 1;
}
The method above will cycle through all visible cells and start animating the activity indicator.
To handle the case of the tableview being scrolled, I re-animate the indicators using the method below which is in cellForRowAtIndexPath. cellStateKey simply indicates if the cell has a checkmark next to it, if it does have a checkmark and my activityflag (async webserver call in progress)is set..then i want to continue animating.(technically re-start animation, as scrolling tableview stops it)
if ([[rowData objectForKey:cellStateKey] boolValue]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
if(cell.accessoryType == UITableViewCellAccessoryCheckmark &&activityFlag ==1){
for (UIView *sub in [[cell contentView] subviews]) {
if (sub.tag == 1) {
UIActivityIndicatorView *acView = (UIActivityIndicatorView *)
[cell.contentView viewWithTag:1];
[acView startAnimating];
}
}
As mentioned in origional question I initialise my activity indicators (and remove activity indicators aswell if required) in the method didSelectRowAtIndexPath
You can add UIActivityIndicatorView as cell's accessoryView.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.frame = CGRectMake(0, 0, 24, 24);
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryView = spinner;
[spinner startAnimating];
[spinner release];
}
A simple way to do this (assuming you're adding the indicator as in your code) is to first get a collection of the visible cells (or rows) in your table, by calling [tableView visibleCells]. Then iterate through the cells like this:
for (UITableViewCell *cell in [tableView visibleCells]) {
for (UIView *sub in [cell subViews]) {
if (sub.tag == 1) { // "1" is not a good choice for a tag here
UIActivityIndicatorView *act = (UIActivityIndicatorView *)sub;
[act startAnimating]; // or whatever the command to start animating is
break;
}
}
}
There's more complexity for you to deal with: in your original code, you need to make sure you're not adding an additional activity indicator to a pre-existing cell each time cellForRowAtIndexPath is called, and you need to account for the situation where the user might scroll the table at a later point, exposing cells that do not have their activity indicator turned on.
You need to make a custom UITableViewCell by extending it. Then have a UIActivityIndicatorView as a member of that Cell. Then you can access it and control it on a cell by cell basis.
Assuming that activity view indicator tag is unique in the cell.contentView you can try something like:
UITableViewCell *cell = [tableview cellForRowAtIndexPath:indexpath];
UIActivityIndicatorView *acView = (UIActivityIndicatorView *)[cell.contentView viewWithTag:1];
//acView will be your activitivyIndicator

UITableViewCell with custom UIButton

I am trying to create a UITableView with a custom UIButton in each table cell. I implemented like this..
#implementation CouponDetailsCustomTableViewCell
...............
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
[self setBackgroundColor:[UIColor whiteColor]];
CGRect frame = self.contentView.frame;
self.radioButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.radioButton setImage:[UIImage imageNamed:#"radio_blank.png"] forState:UIControlStateNormal];
[self.radioButton setImage:[UIImage imageNamed:#"radio_selected"] forState:UIControlStateSelected];
[self.radioButton setFrame:CGRectMake(16, 10, 29, 29)];
[self.radioButton addTarget:nil action:#selector(radioButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:radioButton];
}
#end
and UITableView Delegate as......
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *COUPON_CELL_ID = #"CouponCell" ;
CouponDetailsCustomTableViewCell * couponCell = (CouponDetailsCustomTableViewCell *) [tableView dequeueReusableCellWithIdentifier:COUPON_CELL_ID];
if (couponCell == nil) {
couponCell = [[[CouponDetailsCustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:COUPON_CELL_ID] autorelease];
couponCell.selectionStyle = UITableViewCellSelectionStyleNone;
}
[couponCell.radioButton setSelected:NO];
return couponCell;
}
and the radioButtonPressed method is
-(void)radioButtonPressed:(id) sender
{
[sender setSelected:YES];
}
Now i run the program and a custom UIButton is shown in every table row . If i click on a button it gets selected (shows the radio_selected.png).
The problem arises when i scroll down the table (i am showing only 4 rows of the table in the window) . When i scroll up again..what i see is the clicked button is showing the radio_blank.png.
I am new in iPhone development. i dont know why is this happening. The most i can guess is the button property is changing .. setSelected:NO.
Someone please give me suggestions to fix this problem.
Thank you.
When you scroll your UITableView, hidden cells are not rendered anymore and might be reused for cells that are becoming visible. If a new cell becomes visible, tableView:cellForRowAtIndexPath: gets called.
The problem is that you're setting the selected state there:
[couponCell.radioButton setSelected:NO];
Therefore, whenever you scroll your cell out of the visible area and back again, it gets reset to selected = NO.
I suggest you create a NSMutableDictionary where you store the selection state of each row/NSIndexPath, which you then re-apply in tableView:cellForRowAtIndexPath:.
replace [couponCell.radioButton setSelected:NO]; in tableView:cellForRowAtIndexPath: with code that sets the selected property depending on a state from your dataSource.
something along those lines:
/* get the button state from your data source */
FancyCouponObject *coupon = [self.coupons objectAtIndex:indexPath.row];
BOOL buttonState = coupon.buttonState;
[couponCell.radioButton setSelected:buttonState];
The cells of a tableView are reused when they are moved off screen. You can't save state in them.
problem is when you scroll the table at that time your cellForRowAtIndexPath: delegate method called for every row... so here when its called at time your setSelected Method call with NO argument like bellow...
[couponCell.radioButton setSelected:NO];
so when you scroll table at time your setSelected method call and your button turn with radio_blank.png
...
:)
-(void)radioButtonPressed:(id) sender
{
[sender setSelected:YES];
}
in this method you are setting button as selected and for selected button you have set the radio_blank.png image

iPhone:How to get the button action of a customcell from the viewcontroller?

I use UITableview, using IB I defined a UIButton in a custom cell and some labels, The custom cell subclass already have definitions of IBAction of the button and necessary IBOutlets on it, but I want to handle the button click events in the tableview controller it self but not in the custom cell subclass.
How can I do this? also I need to get which row's button is exactly clicked so I will show the content relavant to it.
I solved problem by addding this into my controller;
[cell.expButton setTag:indexPath.row];
UIButton *button = (UIButton *)[cell expButton];
[button addTarget:self action:#selector(buttonTapped:event:) forControlEvents:UIControlEventTouchUpInside];
For the need of clickable event(touch effect on the subView), I usually do this :
-(void)viewDidLoad{
self.tableView.delaysContentTouches = NO;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// This will do the touch down effect on the subView of your UIButton
for (id obj in cell.subviews)
{
if ([NSStringFromClass([obj class]) isEqualToString:#"UITableViewCellScrollView"])
{
UIScrollView *scroll = (UIScrollView *) obj;
scroll.delaysContentTouches = NO;
break;
}
}
// I need to get which row's button is exactly clicked so I will show the content relavant to it.- here it is
UIButton *btn = (UIButton*)[cell viewWithTag:200];
// btn = cell.myButton; If you are connected with the IBOutlet
[btn setTag:200 + indexPath.row];
[self.btnPlay addTarget:self action:#selector(btnAction:) forControlEvents:UIControlEventTouchUpInside];
}
-(void)btnAction: (id)sender{
UIButton *btn = (UIButton *)sender;
NSLog(#"Selected subView of the Row is %d th tag", btn.tag-200);
// You can get the UITableViewCell using the tag property
selectedIP = [NSIndexPath indexPathForRow:btn.tag - 200 inSection:1;
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:selectedIP];
// Do the relevant code
}
the way how you want to do this is right (how MVC have to coded clean) - thats beautiful!
To make it more efficent you have to use delegation - thats the best way how to do this!
Here a way how you can do this (e.g. in swift).
https://stackoverflow.com/a/29920564/1415713

How to maintain a selected state, when clicks the UIButton in iPhone?

I have created three buttons and set the background image for the normal state and the selected state. When i click the button, changed one image as the selected state. But when is scroll the table view, the selected image is not retained (previous selected cell) which means it comes for the normal state.
My code is,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
likeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *likeSelectedImage = [UIImage imageNamed:#"like.png"];
//set image as background for button in the normal state
[likeBtn setBackgroundImage:likeSelectedImage forState:UIControlStateNormal];
[likeBtn addTarget:self action:#selector(likeAction:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:likeBtn];
}
My Button action is,
-(void) likeAction : (id) sender
{
UIImage *likeSelectedImg = [UIImage imageNamed:#"like-selected.png"];
UIImage *likeImg = [UIImage imageNamed:#"like.png"];
if ([sender isSelected]) {
[sender setImage:likeImg forState:UIControlStateNormal];
[sender setSelected:NO];
}else {
[sender setImage:likeSelectedImg forState:UIControlStateSelected];
[sender setSelected:YES];
}
}
SO my problem is, when i scroll the table view cell, the previously selected state of an image is not retained. Because the cellForRowAtIndex method has repeatedly called, when i scrolled the table. so it automatically sets "[likeBtn setBackgroundImage:likeSelectedImage forState:UIControlStateNormal];". How to avoid this problem?
So please help me out?
Thanks!
You are seeing this due to the design pattern that UITableView's use for displaying data. To minimize resource usage only the table cells that are currently on screen are guaranteed to be kept in memory. As cells are scrolled off-screen the cell objects are intended to be recycled to use for the new cell's appearing at the other end of the list and therefore lose their state.
I'm assuming you've trimmed your code somewhat as you're returning a cell in tableView: cellForRowAtIndexPath: but there's no reference to that variable anywhere else in the snippet you posted. This makes it impossible to see how you're getting the cell before adding the button.
Bit of a stab in the dark as to how your code works but here's a high level overview of what you need to do to retain state.
Create some controller-level storage such as an NSArray to hold all your button states for all the table rows.
In the likeAction determine which row the button is from (possibly assign the row number to the button's tag property when creating it) and update the state for the corresponding row in your NSArray
In tableView: cellForRowAtIndexPath: set up your button with the correct image by using the state which is fetched from the NSArray for the given indexPath.
the above one is not correct in syntax:
cell = [[[ToDoSummaryTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
give each cell a unique identifier, it should work.
See any sample code which uses 'reuseidentifier' in XCode IOS api.
Thanks,
Bharath
I re-edit your above code. I hope, I will help you.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
likeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *likeSelectedImage = [UIImage imageNamed:#"like.png"];
//set image as background for button in the normal state
[likeBtn setBackgroundImage:likeSelectedImage forState:UIControlStateNormal];
[likeBtn setBackgroundImage:likeSelectedImage forState:UIControlStateHighlighted];
[likeBtn addTarget:self action:#selector(likeAction:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:likeBtn];
}
You can use 'reuseIdentifier' for each cell with an identifier.
cell = [[[ToDoSummaryTableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault reuseIdentifier:.<identifier>] autorelease];

UIButton in UITableViewCell

I have a UIButton with an image inside of a UITableViewCell. When the cell is being highlight, the button is also entering the highlighted state (i.e. a darker shade of the image), regardless of whether the user is clicking within the bounds of the button or not.
I don't want this functionality - I only want the button to be highlighted when the button is clicked, not when the entire cell is being clicked.
I've tried to set the image in the highlighted state to be the same as the normal image. This fixes the issue however it stops the button from changing color when it really is highlighted.
Any ideas how to achieve the desired effect?
This was driving me crazy. I figured out that you need to override setHighlighted:animated: and setSelected:animated:
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
[super setHighlighted:highlighted animated:animated];
self.yourButton.highlighted = NO;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
self.yourButton.selected = NO;
// If you don't set highlighted to NO in this method,
// for some reason it'll be highlighed while the
// table cell selection animates out
self.yourButton.highlighted = NO;
}
One approach would be to "deselect" or "unhighlight" the button when the table view cell is selected:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[yourButton setHighlighted:NO];
// do something cool
}
codecaffeine's suggestion didn't work for me (iOS 8.3), but it did put me on the right track. I modified it like this (it's in Swift though):
override func setHighlighted(highlighted: Bool, animated: Bool) {
var colorBefore = self.myButton.backgroundColor
super.setHighlighted(highlighted, animated: animated)
self.myButton.highlighted = false
self.myButton.backgroundColor = colorBefore
}
I've used a different approach, it's a little bit easier, I hope it works for you. Just set the button's highlighted state to false inside the two above delegate methods:
-(void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath {
UIButton *btnAction = (UIButton *) [[tableView cellForRowAtIndexPath:indexPath] viewWithTag:3];
btnAction.highlighted = NO;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UIButton *btnAction = (UIButton *) [[tableView cellForRowAtIndexPath:indexPath] viewWithTag:3];
btnAction.highlighted = NO;
}
Haven't actually tried this, but could you maybe add a target / action to the button for UIControlEventTouchDown that updated its highlighted-state image to what you wanted, then another target / action for UIControlEventTouchUpInside / UIControlEventTouchUpOutside / UIControlEventTouchCancel that reset the highlighted image to match the normal-state image?
A possible workaround would be that, you set the cell selection style to be none. In that case when you select the cell, it would not be highlighted.
This is only, the possible workaround. May be you have other things in your mind.
To make it work, I had to use the following subclassing on the UITableViewCell. The 'button' object is the button inside the custom cell:
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
[self.button setShowsTouchWhenHighlighted:NO];
[super setHighlighted:highlighted animated:animated];
self.button.highlighted = NO;
[self.button setShowsTouchWhenHighlighted:YES];
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[self.button setShowsTouchWhenHighlighted:NO];
[super setSelected:selected animated:animated];
self.button.highlighted = NO;
// Configure the view for the selected state
[self.button setShowsTouchWhenHighlighted:YES];
}
Interverting the various line may result in the highlighting of the button.