Very simple situation, multiple rows and only one row should have the checkmark at a time. User selects row X, row X gets checkmark, checkmark removed from previously selected row. But how to animate the change? (Fade in fade out).
I thought maybe this would work:
[UIView animateWithDuration:1.0 animations:^{
cell.accessoryType = UITableViewCellAccessoryNone;
cell.accessoryType = UITableViewCellAccessoryCheckmark;
previous.accessoryType = UITableViewCellAccessoryCheckmark;
previous.accessoryType = UITableViewCellAccessoryNone;
}];
and also
[tableView reloadRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
The first did nothing, the second deleted both of the cells referred by the NSIndexPath's in the paths array. I also tried wrapping reloadRowsAtIndexPaths in [tableView beginUpdates/endUpdates].
UPDATE:
Calling reloadRowsAtIndexPath on the incoming indexPath (trivial implementation below) is doing the same thing, the row "goes blank".
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
The table is a static table, I have properties declared and synthesized for each cell, cellForRowAtIndexPath returns the corresponding cell for each row...
.h
#property (strong, nonatomic) IBOutlet UITableViewCell *cellNever;
#property (strong, nonatomic) IBOutlet UITableViewCell *cell030;
#property (strong, nonatomic) IBOutlet UITableViewCell *cell100;
.m
#synthesize cellNever;
#synthesize cell030;
#synthesize cell100;
...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [cells objectAtIndex:indexPath.row];
}
- (void) viewDidLoad {
cells = [NSArray arrayWithObjects:cellNever, cell030, cell100, cell130, cell200, cell230, cell300, cell400, cell500, nil];
}
Many thanks.
Related
I have a UITableViewController which consists of a TableView and a UILabel. UITableViewSource feeds data to the TableView through custom UITableViewCell. UITableViewCell consists of a Stepper which can add items and the Label holds that value.
I want to update the UILabel in UIViewController when the user taps on the Stepper and i cannot seem to get that to work...
Any help/tips are greatly appreciated! Here is sample code from my test project, there is a bunch of unused code but this is just a test so i let it be there.
Thanks,
The Code is
My Custom Cell Class is "StepperDetailsCell".
#interface StepperDetailsCell : UITableViewCell
#property (nonatomic,weak) id <StepperDetailsCellDelegate> delegate;
#property (strong, nonatomic) IBOutlet UIImageView *imagegroceries;
#property (strong, nonatomic) IBOutlet UIStepper *objstepper;
#property (strong,nonatomic) IBOutlet UILabel *lblsteppervalue;
-(IBAction)stepperclicked:(UIStepper*)sender;
#end
and My table view class is named as "SecondViewController"
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *simpleTableIdentifier = #"Cell";
cell = (StepperDetailsCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil){
cell = (StepperDetailsCell *)[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
[cell.imagegroceries setImage:[UIImage imageNamed:[[self.didSelectedImages sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)]objectAtIndex:indexPath.row]]];
[cell.objstepper addTarget:self action:#selector(stepperValuechanges:) forControlEvents:UIControlEventValueChanged];
cell.lblsteppervalue.text = [self.quantityArr objectAtIndex:indexPath.row];
return cell;
}
pragma mark - Stepper Action Method
-(void)stepperValuechanges:(UIStepper *)sender{
CGPoint stepperPosition = [sender convertPoint:CGPointZero toView:self.tableView];
indexPath1 = [self.tableView indexPathForRowAtPoint:stepperPosition];
if (indexPath1 != nil )
{
float val = sender.value;
valueInt = (int)val;
[self.quantityArr replaceObjectAtIndex:indexPath1.row withObject:[NSString stringWithFormat:#"%i",valueInt]];
NSLog(#"%#",self.quantityMDict);
}
[self.tableView reloadData];
}
Here, Everything is working properly,but whenever new cell is loading from bottom of tableview, then that time new cell stepper act as disappeared cell stepper.
Finally After 3 days I solved my problem.
Just set the value for UIStepper before firing the action of the UIStepper Object to the corresponding Controller Object like the following way..
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *simpleTableIdentifier = #"Cell";
cell = (StepperDetailsCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil){
cell = (StepperDetailsCell *)[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
[cell.imagegroceries setImage:[UIImage imageNamed:[[self.didSelectedImages sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)]objectAtIndex:indexPath.row]]];
// I just set the previous value(this value, I am taken from the array named "quantityArr") to corresponding stepper, before firing the action to the controller object from the stepper object. That's it..
[cell.objstepper setValue:[[self.quantityArr objectAtIndex:indexPath.row] doubleValue]];
[cell.objstepper addTarget:self action:#selector(stepperValuechanges:) forControlEvents:UIControlEventValueChanged];
cell.lblsteppervalue.text = [self.quantityArr objectAtIndex:indexPath.row];
return cell;
}
I often faces this issue when developing tableview custom cell.
Here is problem, I have a tableview and it has lots of customized cell (a UIImageView and UILabel) When user tap any of this cell pushes new UIViewController and user filling some data and tapped the "Save" viewcontroller push back with delegation method.
In this delegate method I inspect tapped cell and change that tint color (like selected state but I'm only changing custom imageview tint color). So this changes correctly but when I'm scrolling any vertical direction tint color disappear. Below pictures and code for figuring out correctly.
When pop to view controller from delegate method (works correctly)
When scrolling vertical direction tin
// Custom cell
#interface CustomCell : UITableViewCell
#property(strong, nonatomic) UIImageView *imageView;
#property(strong, nonatomic) UILabel *titleLabel;
#end
// Custom Cell implementation nothing special here.
// UIViewController delegate method when pop back
// I'm filling specific color
#interface UIViewController
#property (strong,nonatomic) CustomCell *myCustomCell;
#end
#implementation UIViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
_myCustomCell = (CustomCell *)[self.tableView dequeueReusableCellWithIdentifier:#"CustomCell" forIndexPath:indexPath];
...
}
- (void)userTappedBackButton {
_myCustomCell.imageView.image = [cell.customImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
_myCustomCell.imageView.tintColor = [UIColor colorWithRed:0.27 green:0.58 blue:0.98 alpha:1];
}
#end
The problem you have is that you are saving a reference to the UITableViewCell but the cells are being reused.
You need to save the information about the cell to be highlighted in another manner, that cannot be influenced by the cells being reused. I would suggest by using the indexPath.
Something like the following should work:
#property (nonatomic, strong) NSIndexPath *lastSelectedIndexPath;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
CustomCell *cell = (CustomCell *)[self.tableView dequeueReusableCellWithIdentifier:#"CustomCell" forIndexPath:indexPath];
if([indexPath isEqual:self.lastSelectedIndexPath]) {
cell.imageView.image = [cell.customImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
cell.imageView.tintColor = [UIColor colorWithRed:0.27 green:0.58 blue:0.98 alpha:1];
}
...
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Save the selected indexPath
self.selectedIndexPath = indexPath;
}
- (void)userTappedBackButton {
// Reload the row that needs to be updated with the new tint color
[tableView reloadRowsAtIndexPaths:#[self.selectedIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
Let me know if it worked out.
Keep track of the index path of the tapped cell when it is selected rather than the cell itself, which is reused as you scroll. Apply the selected styling in cellForRow when you're presenting the cell for the matching "selected" index path, in addition to when you return back to the view
Update: adding code to clarify
In your custom cell, provide simple ways to enable/disable the tint:
#interface CustomCell : UITableViewCell
#property(strong, nonatomic) UIImageView *imageView;
#property(strong, nonatomic) UILabel *titleLabel;
- (void)enableLastSelectedHighlight;
- (void)disableLastSelectedHighlight;
#end
Implementation:
#implementation CustomCell
- (void)prepareForReuse
{
[super prepareForReuse];
// Reset prior to being reused
[self disableLastSelectedHighlight];
}
- (void)enableLastSelectedHighlight
{
self.imageView.image = [cell.customImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
self.imageView.tintColor = [UIColor colorWithRed:0.27 green:0.58 blue:0.98 alpha:1];
}
- (void)disableLastSelectedHighlight;
{
self.imageView.image = [cell.customImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
}
#end
In your TableViewController.m file, keep track of the selection, either via didSelectRowAtIndexPath, or your existing custom delegate userTappedBackButton. The implementation of the will be the same:
#interface MyTableViewController ()
#property (nonatomic, strong) NSIndexPath *lastSelectedCellIndexPath;
#end
#implementation MyTableViewController
// Your existing implementation
// ...
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Update our reference to the tinted row
[self setLastSelectedCellIndexPath:indexPath];
// Un-tint any currently tinted cells
[self.tableView.visibleCells makeObjectsPerformSelector:#selector(disableLastSelectedHighlight)];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Get your cell
CustomCell *cell = (CustomCell *)[self.tableView dequeueReusableCellWithIdentifier:#"CustomCell" forIndexPath:indexPath];
if (indexPath == self.lastSelectedCellIndexPath) {
// This cell should be tinted
[cell enableLastSelectedHighlight];
}
// The rest of your cell setup...
}
#end
I have created a UITableView in Storyboard and it is dynamically cell. The problem is that when there are not enough cells to reuse, it randomly empties a few of my cells. I think this is logical, but I want to resolve this problem.
I give an example:
I have a UITableView that is capable to generate 10 cells in a view. But now, I only want to show 8 cells out of 10. It gives no problem when I have only a section. With more than 1 section, it will always empty 2 cells and show 8 cells, but it should show 10 cells.
Can anyone help me with this? Thank you.
Updated With Source code
#pragma mark - List View
#pragma mark - Table View Delegate
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section {
// Number of rows is the number of time zones in the region for the specified section.
return self.listCollection.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ListCell" forIndexPath:indexPath];
for (id object in cell.contentView.subviews) {
[object removeFromSuperview];
}
[cell.contentView addSubview:[self.listCollection objectAtIndex:indexPath.row]];
return cell;
}
self.listCollection has an array of UIView Object.
Updated With Images:
Image 1
Image 2
It is happening because you are using 2 sections but are not specifying the content for each section separately. To understand this we need to look into the description of addSubview: method from Apple Documentation
This method establishes a strong reference to view and sets its next responder to the receiver, which is its new superview.
Views can have only one superview. If view already has a superview and that view is not the receiver, this method removes the previous superview before making the receiver its new superview.
Have a good look at bold section in second paragraph. As you are using the same view object from listCollection to populate both the section, so newest created cell will become the superview for this view object and previous cell will be left out with nothing but the plane contentView. You can get the real feel by assigning some default color to the cell contentView. Your blank cell will be displaying the color in full content while other cells will display the view
cell.contentView.backgroundColor = [UIColor greenColor];
Solution
I will recommend to use 2 different datasource for both sections as explained below.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section {
// Number of rows is the number of time zones in the region for the specified section.
if(section==0)
return self.listCollection.count;
else
return <row count for section 1>
}
Same way you need to modify the cellForRowAtIndexPath: code for each section.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ListCell" forIndexPath:indexPath];
for (id object in cell.contentView.subviews) {
[object removeFromSuperview];
}
if(section==0)
[cell.contentView addSubview:[self.listCollection objectAtIndex:indexPath.row]];
else
[cell.contentView addSubview:[<second array of views> objectAtIndex:indexPath.row]];
return cell;
}
But if that is your case that you have to use same array, then you can use this technique. Thanks to Rishi for that.
Last but not least, you should use a custom UITableViewCell class as Michal Zygar has suggested in his post.
UIView *tempView = [self.listCollection objectAtIndex:indexPath.row];
NSData *tempArchiveView = [NSKeyedArchiver archivedDataWithRootObject:tempView];
UIView *viewOfSelf = [NSKeyedUnarchiver unarchiveObjectWithData:tempArchiveView];
[cell.contentView addSubview:viewOfSelf];
You are using the UITableView wrong way.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ListCell" forIndexPath:indexPath];
for (id object in cell.contentView.subviews) {
[object removeFromSuperview];
}
[cell.contentView addSubview:[self.listCollection objectAtIndex:indexPath.row]];
return cell;
}
This is highly inapriopriate. Your listCollection should contain model data. In - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath you should configure the cell with this data.
So you should subclass the UITableViewCell for example
#interface ApartmentCell:UITableViewCell
#property (weak, nonatomic) UILabel* floor;
#property (weak, nonatomic) UILabel* unit;
#property (weak, nonatomic) UILabel* type;
#property (weak, nonatomic) UILabel* area;
#property (weak, nonatomic) UILabel* price;
#end
and then
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ApartmentCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ListCell" forIndexPath:indexPath];
Apartment* apartment=self.listCollection[indexPath.row];
cell.unit.text=apartment.unit;
//and so on with other fields
return cell;
}
So I've used this tutorial to populate a UITableView with custom cells that represent balances. When stepping through the code, I witness the correct amount of cells get created (only 4 with the current test data) and their labels' text set correspondingly.
My problem is when the table is displayed on the screen, only the first row/cell is displayed.
Any insight as to why this could be occurring would be greatly appreciated!
Removed old code.
BalanceCell.h:
#import <UIKit/UIKit.h>
#interface BalanceCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UILabel *nameLabel;
#property (weak, nonatomic) IBOutlet UILabel *amountLabel;
#property (weak, nonatomic) IBOutlet UILabel *modifiedLabel;
#end
EDIT:
My TableView delegate methods are now as follows:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [_balances count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
BalanceCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[BalanceCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundColor = [_hex colorWithHexString:_themeColourString];
return cell;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(BalanceCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
Balance *item = [_balances objectAtIndex:indexPath.row];
cell.nameLabel.textColor = _themeColour;
cell.nameLabel.text = item.name;
cell.amountLabel.textColor = _themeColour;
cell.amountLabel.text = [NSString stringWithFormat:#"%#%#", item.symbol, item.value];
cell.modifiedLabel.textColor = _themeColour;
cell.modifiedLabel.text = [NSString stringWithFormat:#"%#", item.modified];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 94;
}
As #Sebyddd suggested, I now register the NIB in the viewDidLoad method.
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:#"BalanceCell" bundle:nil] forCellReuseIdentifier:#"Cell"];
}
These changes may make my code more correct but still only the first cell is displayed.
If cells are getting created and returned properly I guess height is not being set propery. By default I beleive all cells have a height of 44. If your cell exceeds this height it might not get displayed.
You can tell the tableview to adjust height for every cell using (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath delegate
In that delegate just return your cells height.
EDIT:
You are using dequeueReusableCellWithIdentifier: which will return A UITableViewCell object with the associated identifier or nil if no such object exists in the reusable-cell queue.
Instead use dequeueReusableCellWithIdentifier:forIndexPath: which will return A UITableViewCell object with the associated reuse identifier. This method always returns a valid cell.
You need to register the nib/class for that custom cell in viewDidLoad
Try this:
if (cell == nil) {
[tableView registerNib:[UINib nibWithNibName:#"BalanceCell" bundle:nil] forCellReuseIdentifier:#"Cell"];
cell = [[BalanceCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
Use this tuto : http://www.appcoda.com/uitableview-tutorial-storyboard-xcode5/ , your tuto is a bit outdated, and hard to follow !
I would like to hide an expanded tableviews label, when the cell is expanded and hide a button when it is collapsed. I have my cell implementation in another class, with the property of the label and the button in the header. The problem is that when I call these cell methods in the ExpandedViewController, the code goes into the method, but it won't change the properties behaviour. Could you possibly help me with this issue?
Thank you
ExpandedCell.h
#property (nonatomic, retain) IBOutlet UILabel *lblTitle;
#property (strong, nonatomic) IBOutlet UIButton *setTime;
ExpandedCell.m
(void)setIfHidden:(BOOL)showIfHidden
{
if (showIfHidden)
{
[self.lblTitle setHidden:YES];
[self.setTime setHidden:NO];
}
else
{
[self.lblTitle setHidden:NO];
[self.setTime setHidden:YES];
}
}
ExpandedViewController.m
import ExpandedCell.h
.
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([indexPath isEqual:self.expandedIndexPath])
{
return CELL_HEIGHT_EXPANDED;
}
else
{
return CELL_HEIGHT_COLLAPSED;
}
}
(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.expandedIndexPath = ([self.expandedIndexPath isEqual:indexPath]) ? nil : indexPath;
ExpandedCell *hideCell = [[ExpandedCell alloc] init];
showIfHidden = YES;
[hideCell setIfHidden:showIfHidden];
[tableView beginUpdates];
[tableView endUpdates];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Your properties are labelled as IBOutlets. You create a new instance of the cell with [[ExpandedCell alloc] init].
You have a few issues:
By calling alloc init the IBOutlets won't exist because the instance isn't unarchived from a NIB file.
Once you create hideCell, you call a method on it and then it gets destroyed (because nothing retains it).
You shouldn't be creating a new cell, you should be accessing and updating the existing one, so you should be using:
ExpandedCell *hideCell = [tableView cellForRowAtIndexPath:indexPath];