I have a UICollectionView, which is populated with cells, and each cell has a gradient button (MKgradientbutton https://github.com/mikekatz/iOS-UI-Utils/tree/master/Classes/Button%20Widgets). The view is 'changed' by pressing one of 5 other buttons, which in turn loads the data for the buttons into an array which provides the datasource for the uicollectionview. One of the paramaters is the gradient button colour. However on reloading the datasource and performing a [self.collectionView reloadData] the buttons title and the other paramaters will change but the colour will quite often change to completely the wrong one. Repeatedly reloading the data will solve this problem - however, even more strangely, one press of the button seems to correct the colour of the button! I have NSLogged the datasource array on the line before the reloadData takes place and all of the colours are correct. I have pasted some code below - please bear with me as the app is still in the very early stages of development!
I really can't see any issues with the code - and I believe that the UICollectionView needs 'redrawn' but I assumed [self.CollectionView reloadData] would solve that!
Pulling data from sqlite db into and object then adding to array:
Bird *bird = [[Bird alloc] init];
bird.birdName = [results stringForColumn:#"name"];
bird.buttonColour = [results stringForColumn:#"btncolour"];
[birds addObject:bird];
The array 'birds' being used to populate UICollectionView:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView2 cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CollectionViewCell *cell = [collectionView2 dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
Bird *bird = [birds objectAtIndex:(indexPath.row)];
//Check button colour against index path of item
NSLog(#"****INDEX PATH %i",indexPath.section*2 + indexPath.row);
NSLog(#"****colour %#",bird.buttonColour);
[cell.button setTitle:bird.birdName forState:UIControlStateNormal];
[cell.button setTag:[birds indexOfObject:bird]];
if ([bird.buttonColour isEqualToString:#"Green"]) {
[cell.button setButtonColor:[UIColor greenColor]];
}
if ([bird.buttonColour isEqualToString:#"Orange"]) {
[cell.button setButtonColor:[UIColor orangeColor]];
}
if ([bird.buttonColour isEqualToString:#"Red"]) {
[cell.button setButtonColor:[UIColor redColor]];
}
if ([bird.buttonColour isEqualToString:#"Blue"]) {
[cell.button setButtonColor:[UIColor blueColor]];
}
cell.button.titleLabel.numberOfLines = 0;
cell.button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
cell.button.titleLabel.textAlignment = NSTextAlignmentCenter;
return cell; }
collectionViewCell.h (the gradient button is the only item in the cell):
#import <UIKit/UIKit.h>
#import "MKGradientButton.h"
#interface CollectionViewCell : UICollectionViewCell
#property (nonatomic) IBOutlet MKGradientButton* button;
#end
You may have to tell the button to redraw itself via [cell.button setNeedsDisplay].
Related
I have a CollectiveView with a variety of elements inside. I have managed to add a button and have attached an action to it. On pressing the button, I want that call to perform a certain activity, like change background colour for instance...
At the moment, this activity is being applied to all the cells. Any suggestions appreciated.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
cell.label.text = [NSString stringWithFormat:#"%ld",(long)indexPath.item];
cell.trackLbl.text = [NSString stringWithFormat:#"Track %ld", (long)indexPath.row + 1];
cell.trackTitle.image = [UIImage imageNamed:[[self.util trackTitleImages] objectAtIndex:indexPath.row]];
[cell.record addTarget:self
action:#selector(recording:)
forControlEvents:UIControlEventTouchUpInside];
if (isRecording) {
[cell.spinnerWidget beginCompletion:[UIColor redColor]];
}
else {
[cell.spinnerWidget beginCompletion:[UIColor colorWithRed:0.45f green:0.77f blue:0.87f alpha:1.0f]];
}
[self.view setNeedsDisplay];
return cell;
}
So as you can see, at the moment I have the 'recording' target assigned to my cell uibutton. From this, I can also get the index of the cell. My work around was to have a bool (isRecording) and reloadData. But obviously this gets applied to all cells.
If the button is in the cell use superview. For example in the case of background color
#IBAction func buttonPressed(sender: AnyObject) {
yourButton.superview.backgroundColor = UIColor(your values here)
}
This is a follow-up question to my previous one and this time I have a problem with UIButton that I have added in each UICollectionViewCell. Before diving into the problem, let me brief out all I've got so far.
Here's the basic rundown of how my UICollectionView in Scrolling Filmstrip style within UITableView works:
Create a normal UITableView with a custom UITableViewCell
Create a custom UIView that will be added to the cell's contentView
The custom UIView will contain a UICollectionView
The custom UIView will be the datasource and delegate for the
UICollectionView and manage the flow layout of the UICollectionView
Use a custom UICollectionViewCell to handle the collection view data
Use NSNotification to notify the master controller's UITableView when
a collection view cell has been selected and load the detail view.
So far, I've been able to add UIButton in each UICollectionViewCell and when I tap the UIButton its image will change to checked mark but the problem occurs when I scroll up/down. Once the cell with checked mark UIButton is off-screen and scrolled back on-screen again, UIButton image starts to get messed around. For example, when UIButton image in cell1 should be checked mark but it's not. Instead checked mark will be appeared in another cell.
Here below is my relevant code:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
PostsCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"PostsCollectionViewCell" forIndexPath:indexPath];
NSDictionary *cellData = [self.collectionData objectAtIndex:[indexPath row]];
cell.postTextLabel.text = [cellData objectForKey:#"text"];
cell.locationNameLabel.text = [cellData objectForKey:#"locationName"];
// >>> Select Button <<<
UIButton *selectButton = [UIButton buttonWithType:UIButtonTypeCustom];
[selectButton setFrame:CGRectMake(110, 150, 20, 20)];
[selectButton setImage:[UIImage imageNamed:#"uncheckedDot.png"] forState:UIControlStateNormal];
[selectButton setImage:[UIImage imageNamed:#"checkedDot.png"] forState:UIControlStateSelected];
[cell.contentView addSubview:selectButton];
[selectButton addTarget:self
action:#selector(buttonPressed:)
forControlEvents:UIControlEventTouchUpInside];
// >>> End Select Button <<<<
return cell;
}
// >>> Select Button Method <<<
-(void) buttonPressed:(UIButton *)sender
{
if([sender isSelected]){
//...
[sender setSelected:NO];
} else {
//...
[sender setSelected:YES];
}
}
Any help would be greatly appreciated! Thanks in advance.
Man, you should be aware that cells (both for UITableView and UICollectionView) are reused via the reuse deque under the hood of UIKit and are not obligated to keep their state consistent with regard to their position in a table view or a collection view. What you need to do is to make sure that the return value of dequeueReusableCellWithReuseIdentifier: is then properly initialized (or, in your case, re-initialized) in accordance with the state of your data model. Basically, you need to store the state of your "checkmarks" (or "checkboxes", what have you) in an array or any other data structure. Then, when you call dequeueReusableCellWithReuseIdentifier:, you should apply that stored state to the return value (the cell you're going to return for the collection view) you just got. Hope it's explained good enough. Good luck.
Maintain in your Data source which cell is to be selected by adding a key for each data source dict. For example:#"isSelected"
Then in cellForRowAtIndexPath:
if ([[cellData valueForKey:#"isSelected"] boolValue]) {
[selectButton setSelected:NO];
} else {
[selectButton setSelected:YES];
}
and in your -(void) buttonPressed:(UIButton *)sender Method:
CGPoint point = [self.collectionView convertPoint:CGPointZero fromView:sender];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:point];
NSDictionary *cellData = [self.collectionData objectAtIndex:[indexPath row]];
if ([[cellData valueForKey:#"isSelected"] boolValue]) {
[cellData setValue:#"false" forKey:#"isSelected"];
} else {
[cellData setValue:#"true" forKey:#"isSelected"];
}
[self.framesCollectionView reloadItemsAtIndexPaths:#[indexPath]];
AND
In your cellForRowAtIndexPath set a tag for the UIButton. Then before adding a UIButton as subview to the cell's contentView check if a view with this tag already exists. In case the cell has been reused, just use the already existing button.
Sorry for my poor English. I want to add some clickable buttons in my custom tablecell and it works fine but i face a problem when i scroll down the table. After scrolling, the buttons in the upper cell will have no response when i click on it. but when i scroll back to the original position of the cell, the buttons have response again.
I put this
[cell.btn addTarget:self action:#selector(btnPressed:) forControlEvents:UIControlEventTouchUpInside];
in cellForRowAtIndexPath method;
and btnPressed had been triggered before i scroll down. but btnPressed cannot be triggered after i scroll out the bound of the original position of that cell.
Hope someone can understand my problem thanks.
Please tell me how can I make the button response even the table scroll ? thanks in advance
method 1
when i tried the method addtarget in cellForRowAtIndexPath and the coding is like this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"CustomTableViewCell";
CustomTableViewCell *cell = (CustomTableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:cellIdentifier owner:self options:nil];
cell = [nib objectAtIndex:0];
}
[cell.btn addTarget:self action:#selector(btnPressed:)
return cell;
}
method 2
i also tried another method which do not addtarget in cellForRowAtIndexPath
Here's my code with delegate
in customcell.h, i added this
#protocol CustomCellDelegate <NSObject>
- (void)didClickOnbtn;
#end
in customcell.m, i added this
#interface CustomTableViewCell()
#property (nonatomic, strong) NSDictionary *currentDict;
#property (nonatomic,assign) id<CustomCellDelegate>delegate;
#end
and
- (IBAction)didClickOnbtn:(id)sender {
if (self.delegate && [self.delegate respondsToSelector:#selector(didClickOnbtn)]) {
[self.delegate didClickOnbtn];
}
}
and i have set the custom cell delegate to the tableview
in tabelview controller i also have the method didClickOnbtn and <CustomCellDelegate>
and the button can trigger didClickOnbtn method in tableview controller response before scrolling the table.
UPDATE:
I had made a section header view in the table. And I find out when i delete the section header view, the problem is solved. Although I don't know why the section header view would make the button disable, it works well now. Hope this can help someone who are in the same situation as me.
p.s. Both methods work.
I had the same problem. I used the workaround to use Touch Down instead of Touch Up Inside. It's not a perfect solution but a work around as the IBAction for Touch Down as being called even when scrolling is not finished.
How are you setting the frame of button ?
is it in reference to cell frame ? or hardcode like below
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self
action:#selector(aMethod:)
forControlEvents:UIControlEventTouchUpInside];
[button setTitle:#"MyButton" forState:UIControlStateNormal];
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0); // example co-ordinate
[cell addSubview :button] ;
In this case , after scrolling down the button co-ordinate will not change and wont work as u said best way to make it right is
create a interface file for UITableViewCell , place a button there and do the wiring
this will solve the scroll problem for you.
also you can try doing this .
button.frame = CGRectMake(cell.frame.origin.x+samplevalue ,cell.frame.origin.y+sampleValue , 160.0, 40.0);
Hope this helps.
Adding target for a button in a tableViewCellForRowAtIndexPath: API is not not recommended way. This API gets called mutiple times when app needs to display the cell.
Move this code to cell class. Create custom class if not done and add this code. In the action method of the button, reference your controller and call some API that does action work for the cell (say open up other controller or so). You can make your view controller as delegate of the cell for this purpose.
Hope this change may solve your problem
I am assuming that you want to add the button to second cell of tableView.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
if (indexPath.row == 0)
{
cell.textLabel.text = #"Cell1";
}
else if (indexPath.row == 1)
{
//Add button to cell and use different reuseIdentifier
UITableViewCell *cell1 = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"btnCell"];
UIButton *button = [[UIButton alloc]initWithFrame:CGRectMake(cell1.frame.origin.x+10, cell1.frame.origin.y, self.view.frame.size.width-20, cell1.frame.size.height-10)];
[button setTitle:#"ButtonTitle" forState:UIControlStateNormal];
shareButton.backgroundColor = [UIColor grayColor];
[cell1 addSubview:button];
[shareButton addTarget:self action:#selector(buttonTapped) forControlEvents:UIControlEventTouchUpInside];
return cell1;
}
//Your code...
return cell;
}
In my UITableView Controller I have cells which each contain a UILabel and UISegmentedController. There are about 20 cells in total. The user can select the UISegmentedControl to control whether object is contained in an array or not.
My problem is that as the user scrolls the UISegmentedControl is duplicated and appears as though there are multiple selections for UISegmentedControl. I have a feeling that this is because cells are being reused but unfortunately I don't have a very good understanding of reusing cells.
I have been trying to play around with:
if(cell == nil){}
but I am not sure what should happen there:
Anyway here is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"featuresCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSString *feature = [features objectAtIndex:indexPath.row];
UILabel *titleLabel = (UILabel *)[cell viewWithTag:9999];
titleLabel.text = feature;
UISegmentedControl *segmentedControl = [[UISegmentedControl alloc]initWithItems:[NSArray arrayWithObjects:#"Yes", #"No", nil]];
segmentedControl.frame = CGRectMake(215, 17, 85, 28);
[segmentedControl addTarget:self action:#selector(valueChanged:) forControlEvents: UIControlEventValueChanged];
segmentedControl.tag = indexPath.row;
[cell addSubview:segmentedControl];
if ([selectedFeatures containsObject:feature]) {
segmentedControl.selectedSegmentIndex = 0;
}
else{
segmentedControl.selectedSegmentIndex = 1;
}
return cell;
}
Your help will be greatly appreciated,
Thanks for your time.
cell is never nil so that's not going to help you.
The trick is to mark the cell in some way so as to know whether the segmented control has already been added. The best way is to use the segmented control's tag. Unfortunately you seem to be using the tag for some other purpose, so you'll need to stop doing that:
if (![cell viewWithTag:12345]) {
UISegmentedControl *segmentedControl = // ....;
// ....
segmentedControl.tag = 12345;
[cell.contentView addSubview:segmentedControl];
}
And now, you see, you are guaranteed that there is exactly one segmented control in the cell, and you can find it by calling [cell viewWithTag:12345].
The general rule is that each cell subclass should implement - (void)prepareForReuse to make it ready for configuration in - (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath. I think you can solve your problem by subclassing UITableViewCell, creating each cell with the required UISegmentedControl, and providing a method to configure the segmented control as required.
prepareForReuse should reset the segmented control so that each time you configure your custom cell it is in a known, empty state.
The advantage of this approach is that you are not only reusing the table view cell, but also the segmented control and any other decoration you want the cell to contain. Additionally you have moved the configuration of the cell into the cell object.
Briefly, in the cell subclass init method you create the segmented control and add it as a subview of the contentView.
In the cell subclass configuration method you provide you set the segmented control's segments etc.
In the cell subclass prepareForReuse you remove all those segments and leave the segmented control in a state ready for configuration next time you reuse that cell.
To go even further, start using auto layout to position the segmented control in the cell rather than using a set of hard numbers in a frame. If you get those working on one kind of device that's great but as soon as your cells have different dimensions (in a table view on a different device) things will not look so good.
Try this,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"featuresCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSString *feature = [features objectAtIndex:indexPath.row];
UILabel *titleLabel = (UILabel *)[cell viewWithTag:9999];
titleLabel.text = feature;
UISegmentedControl *segmentedControl;
if ([cell.contentView viewWithTag:kSegmentTag]) { //kSegmentTag a tag
segmentedControl = (UISegmentedControl *)[cell viewWithTag:kSegmentTag];
} else {
segmentedControl = [[UISegmentedControl alloc]initWithItems:[NSArray arrayWithObjects:#"Yes", #"No", nil]];
segmentedControl.frame = CGRectMake(215, 17, 85, 28);
[segmentedControl addTarget:self action:#selector(valueChanged:) forControlEvents: UIControlEventValueChanged];
segmentedControl.tag = kSegmentTag;
[cell.contentView addSubview:segmentedControl];
}
if ([selectedFeatures containsObject:feature]) {
segmentedControl.selectedSegmentIndex = 0;
}
else{
segmentedControl.selectedSegmentIndex = 1;
}
return cell;
}
and in valueChanged:
- (IBAction)valueChanged:(id)sender {
UISegmentedControl *segmentedControl = (UISegmentedControl *)sender;
CGPoint tablePoint = [segmentedControl convertPoint:segmentedControl.bounds.origin toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:tablePoint];
//here indexpath.row give changed row index
}
I have a UITableView that is never consistent. This table view has 2 sections.
I have created 2 UIButtons, they both are practically the same image, but one is grey and one is red.
Every row in the UITableView is supposed to have the grey button when the UITableView first loads, and then if a user taps a button then it switches to the red version of the button.
For some reason, about 80% of the time when I load the view controller with this UITableView on my iPhone, the first 2 rows in the first section will not have a button. What's weird is if I tap my finger where the button is supposed to be 2 things happen:
The functionality attached to the button works and NSLogs in the console in xcode.
The red button appears.
So it's like the grey button is there, it's just invisible. This doesn't work because if a user sees 2 rows without a button they are going to think something is broken or that they can't tap on that row.
Also, if I scroll to the very bottom of my UITableView, and then scroll back up again, the invisible grey buttons will magically appear like they were there the whole time.
Here are the first 3 method implementations that are involved in creating my UITableView:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2 ;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if(section == 0)
return [self.potentiaFriendsInParseFirstNamesArray count];
if(section == 1)
return [self.potentiaFriendsNotInParseFirstNamesArray count];
else return 0;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if(section == 0)
return #"Friends Using The App";
if(section == 1)
return #"Send to Contact";
else return #"nil";
}
And then here is my cellForRowAtIndexPath method implementation which does most of the work in creating the UITableView and which actually creates the UIButton settings for each row. The if else statement really has 2 sets of nearly identical code, the only difference is one is for section 1 of the table view and the other is for section 2:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"SettingsCell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (indexPath.section == 0) {
NSString *firstNameForTableView = [self.potentiaFriendsInParseFirstNamesArray objectAtIndex:indexPath.row];
PFUser *user = [self.allUsersInParse objectAtIndex:indexPath.row];
UIImage *addUserButtonImage = [UIImage imageNamed:#"SliderThumb-Normal-g"];
UIImage *addUserButtonImageHighlighted = [UIImage imageNamed:#"SliderThumb-Normal"];
UIButton *addUserButton = [[UIButton alloc]init];
addUserButton.frame = CGRectMake(237, -10, 64, 64);
[addUserButton setImage:addUserButtonImage forState:UIControlStateNormal];
[addUserButton setImage:addUserButtonImageHighlighted forState:UIControlStateHighlighted];
[addUserButton setImage:addUserButtonImageHighlighted forState:UIControlStateSelected];
[addUserButton addTarget:self action:#selector(handleTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];
addUserButton.tag = indexPath.row;
[cell.textLabel setText:firstNameForTableView];
[cell.detailTextLabel setText:user.username];
[cell.contentView addSubview:addUserButton];
} else {
NSString *firstNameForTableView2 = [self.potentiaFriendsNotInParseFirstNamesArray objectAtIndex:indexPath.row];
NSString *userNameForTableView2 = [self.potentiaFriendsPhoneNumberArray objectAtIndex:indexPath.row];
UIImage *addFriendButtonImage = [UIImage imageNamed:#"SliderThumb-Normal-G"];
UIImage *addFriendButtonImageHighlighted = [UIImage imageNamed:#"SliderThumb-Normal"];
UIButton *addFriendButton = [[UIButton alloc]init];
addFriendButton.frame = CGRectMake(237, -10, 64, 64);
[addFriendButton setImage:addFriendButtonImage forState:UIControlStateNormal];
[addFriendButton setImage:addFriendButtonImageHighlighted forState:UIControlStateHighlighted];
[addFriendButton setImage:addFriendButtonImageHighlighted forState:UIControlStateSelected];
[addFriendButton addTarget:self action:#selector(handleTouchUpInsideForNonUsers:) forControlEvents:UIControlEventTouchUpInside];
addFriendButton.tag = indexPath.row;
[cell.textLabel setText:firstNameForTableView2];
[cell.detailTextLabel setText:userNameForTableView2];
[cell.contentView addSubview:addFriendButton];
}
return cell;
}
I have not included the full method implementations that these 2 statements trigger because they only handle the functionality attached to each button, and most likely have nothing to do with this issue, but I will post them here anyways. These 2 statements can be found in the if else for cellForRowAtIndexPath and handle the touch events for the 2 sections of the table view:
[addFriendButton addTarget:self action:#selector(handleTouchUpInsideForNonUsers:) forControlEvents:UIControlEventTouchUpInside];
[addFriendButton addTarget:self action:#selector(handleTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];
I see the pattern of trying to set up a custom UITableViewCell from within tableView:cellForRowAtIndexPath: quite a lot. The trouble is that I believe it to be the wrong place to do it. If you're really only using a built-in UITableViewCell, then it's the right place; for custom cells, it's not.
I'd recommend making a UITableViewCell subclass with a "setup" method that takes an object and uses that object to set up the cell's UI.
For instance, take section 0 from your UITableView. I'd do something like this:
#interface UserTableViewCell : UITableViewCell
// properties to IBOutlet connections for UI setup
- (void)setupWithUser:(PFUser *)user;
#end
#implementation UserTableViewCell
- (void)setupWithUser:(PFUser *)user {
// Set up your UI with properties from 'user'
}
- (IBAction)buttonPressed:(UIButton *)sender {
// Respond to the button press
}
This way, you can set up the cell's layout as well as images for the button in IB, rather than code. This also makes the code a lot easier to read (and re-read 6 months later!).
Also, unless the 2 cells (section 0 and section 1) are really interchangeable, I'd recommend two different subclasses of UITableViewCell to give you greater control, rather than trying to force one to act like the other or vice versa. Then, you simply assign different identifiers and recycle the appropriate type.