I have an UITableView with expandable rows in section 4. When one row is expanded the others need to be collapsed. I reload specified rows and scroll table to have the expanded one on top. Here's the code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (indexPath.section == 4)
{
if (indexPath.row == 0)
{
if (hypoExpanded)
{
hypoExpanded = NO;
[self performSelector:#selector(reloadRowWithScrollForIndexPath:) withObject:indexPath afterDelay:kAnimationDelay];
}
else
{
hypoExpanded = YES;
moodExpanded = NO;
activityExpanded = NO;
NSArray *indexArray = [NSArray arrayWithObjects:indexPath, [NSIndexPath indexPathForRow:1 inSection:4], [NSIndexPath indexPathForRow:2 inSection:4], nil];
[self performSelector:#selector(reloadRowWithScrollForIndexArray:) withObject:indexArray afterDelay:kAnimationDelay];
}
... }
}
- (void)reloadRowWithScrollForIndexArray:(NSArray *)indexArray
{
[self.tableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:((NSIndexPath *)[indexArray objectAtIndex:0]).row inSection:((NSIndexPath *)[indexArray objectAtIndex:0]).section] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
- (void)reloadRowWithScrollForIndexPath:(NSIndexPath *)indexPath
{
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
switch (section)
{
case 1:
return [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"bgc-info"]];
break;
case 2:
return [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"in-info"]];
break;
case 3:
return [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"bp-info"]];
break;
case 4:
return [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"per-info"]];
break;
}
return nil;
}
Sometimes a random section header (1, 2 or 3) duplicates and overlays the cell that belongs to that section. Scrolling the table to make the section disappear doesn't help. When I scroll back the duplicated header still exists.
It only happens on iOS 6. Invoking performSelectorOnMainThread: is not helpful either. Also, without calling scrollToRowAtIndexPath: everything works fine. What could possibly cause this behaviour?
You can see a screenshot here:
I had the same problem. Check for uncommitted animation in your code
[UIView beginAnimations:nil context:NULL];
... should be committed below
Related
Although UICollectionViewCells is displayed, [collectionView visibleCells] returns nil.
- (void)viewDidLoad{
self.collectionView = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 0, 320, 568) collectionViewLayout:flowLayout];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
[self.collectionView registerClass:[CustomCell class] forCellWithReuseIdentifier:#"Cell"];
[self.view addSubview:self.collectionView];
NSLog(#"%d", [[self.collectionView visibleCells] count]);
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
return cell;
}
Do you have any idea?
The issue is caused by reload has not been finished.
The solution is :
1. [collectionView reloadData];
2. **[collectionView layoutIfNeeded];
3. the get your - > [collectionView visibleCells];
Try this in your view controller
- (void) viewDidLayoutSubviews {
NSLog(#"%d", [[self.collectionView visibleCells] count]);
}
Try add this:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionView?.collectionViewLayout.invalidateLayout()
}
I am trying to update the second last row of a tableview just after the last one (of the same kind) has been inserted. What I want is to change the image in a custom uiimageview i put in the xib.
I achieved this feature by calling [_tableView reloadData] just after the insertion, but this way the insert animation has gone and the performance have got worse.
Here is part of the code I wrote:
•in the delegate:
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
[_dataSource setIsRead:(BOOL)read AtIndexPath:indexPath];
[_dataSource configureCell:(WMChatTableViewCell *)cell AtIndexPath:indexPath];
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
[cell setNeedsLayout];
[cell layoutIfNeeded];
}
• in the dataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
WMMessage *message = [self messageAtIndexPath:indexPath];
/**
Message sent by the user
*/
if ([message.sender_id intValue] == [[[NSUserDefaults standardUserDefaults]valueForKey:kUserIdKey]intValue]) {
WMRightChatTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:rightTextIdentifier];
if (cell == nil) {
cell = [[WMRightChatTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:rightTextIdentifier];
}
[self configureCell:cell AtIndexPath:indexPath];
return cell;
}
/**
Message sent by the recipient
*/
else{
WMLeftChatTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:leftTextIdentifier];
if (cell == nil) {
cell = [[WMLeftChatTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:leftTextIdentifier];
}
[self configureCell:cell AtIndexPath:indexPath];
return cell;
}
return nil;
}
-(void)configureCell:(WMChatTableViewCell *)cell AtIndexPath:(NSIndexPath *)indexPath{
WMMessage *message = [self messageAtIndexPath:indexPath];
....
/**
Last message sent by the recipient
*/
if (!([message.sender_id intValue] == [[[NSUserDefaults standardUserDefaults]valueForKey:kUserIdKey]intValue]) &&
[message isEqual:[[_messagesMatrix lastObject]lastObject]]) {
/**
An highlighted version of the background.
Normally the left cell has the "BoxChat_sx" image, only when it's the last one, the image should change
*/
[cell.balloonImageView setImage:[UIImage imageNamed:#"BoxChat_sx_new"]];
....
}
else if(!([message.sender_id intValue] == [[[NSUserDefaults standardUserDefaults]valueForKey:kUserIdKey]intValue])){
/*
If it's not the last anymore, the image should change back to it's original state
*/
[cell.balloonImageView setImage:[UIImage imageNamed:#"BoxChat_sx"]];
...
}
}
Without calling [_tableView reloadData] this code won't update the imageView. Is there a way to achieve this feature without calling reloadData or, at least, keeping the insertion animation?
I have an UITableView with custom cells and custom headers. When I move one cell upon editing, it pops up on to of the header view.
How can I keep the header view on top of all the cells?
The app uses storyboard, in case that makes a difference.
This is how it looks? https://www.dropbox.com/s/wg8oiar0d9oytux/iOS%20SimulatorScreenSnapz003.mov
This is my code:
[...]
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ListCell";
ListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
int handleSection = [self sectionToHandle:indexPath.section];
switch (handleSection)
{
case PrivateLists:
{
if (tableView.isEditing && (indexPath.row == self.privateLists.count))
{
cell.textField.text = NSLocalizedString(#"Lägg till ny lista", nil);
cell.textField.enabled = NO;
cell.textField.userInteractionEnabled = NO;
cell.accessoryType = UITableViewCellAccessoryNone;
cell.editingAccessoryView.hidden = YES;
}
else
{
List *list = [self.privateLists objectAtIndex:indexPath.row];
cell.textField.text = list.name;
cell.textField.enabled = YES;
cell.textField.userInteractionEnabled =YES;
cell.backgroundColor = [UIColor clearColor];
cell.onTextEntered = ^(NSString* enteredString){
list.name = enteredString;
UpdateListService *service = [[UpdateListService alloc]initServiceWithList:list];
[service updatelistOnCompletion:
^(BOOL success){
DLog(#"Updated list");
NSIndexPath *newPath = [NSIndexPath indexPathForRow:0 inSection:indexPath.section];
[self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];
[self moveListToTop:list.ListId newIndexPath:newPath];
justMovedWithoutSectionUpdate = YES;
}
onError:
^(NSError *error){
[[ActivityIndicator sharedInstance] hide];
[[ErrorHandler sharedInstance]handleError:error fromSender:self];
}];
};
}
}
break;
default:
return 0;
break;
}
return cell;
}
-(UIView *)tableView:(UITableView *)aTableView viewForHeaderInSection:(NSInteger)section
{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 22)];
UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, 300, 21)];
[textLabel setFont:[[AXThemeManager sharedTheme]headerFontWithSize:15.0]];
[textLabel setTextColor:[[AXThemeManager sharedTheme]highlightColor]];
[textLabel setText:#"SECTION TITLE"];
[textLabel setBackgroundColor:[UIColor clearColor]];
UIImageView *backgroundView = [[UIImageView alloc]initWithImage:[AXThemeManager sharedTheme].tableviewSectionHeaderBackgroundImage];
[backgroundView setFrame:view.frame];
[view addSubview:backgroundView];
[view sendSubviewToBack:backgroundView];
[view addSubview:textLabel];
return view;
}
- (float)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 22;
}
- (float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 44;
}
[...]
Good news! I was able to fix/workaround your problem in two different ways (see below).
I would say this is certainly an OS bug. What you are doing causes the cell you have moved (using moveRowAtIndexPath:) to be placed above (in front of) the header cell in the z-order.
I was able to repro the problem in OS 5 and 6, with cells that did and didn't have UITextFields, and with the tableView in and out of edit mode (in your video it is in edit mode, I noticed). It also happens even if you are using standard section headers.
Paul, you say in one of your comments:
I solved it badly using a loader and "locking" the table while
preforming a reloadData
I am not sure what you mean by "using a loader and locking the table", but I did determine that calling reloadData after moveRowAtIndexPath: does fix the problem. Is that not something you want to do?
[self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];
//[self.tableView reloadData];
// per reply by Umka, below, reloadSections works and is lighter than reloadData:
[self reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationNone];
If you dont want to do that, here is another solution that feels a little hacky to me, but seems to work well (iOS 5+):
__weak UITableViewCell* blockCell = cell; // so we can refer to cell in the block below without a retain loop warning.
...
cell.onTextEntered = ^(NSString* sText)
{
// move item in my model
NSIndexPath *newPath = [NSIndexPath indexPathForRow:0 inSection:indexPath.section];
[self.itemNames removeObjectAtIndex:indexPath.row];
[self.itemNames insertObject:sText atIndex:0];
// Then you can move cell to back
[self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];
[self.tableView sendSubviewToBack:blockCell]; // a little hacky
// OR per #Lombax, move header to front
UIView *sectionView = [self.tableView headerViewForSection:indexPath.section];
[self.tableView bringSubviewToFront:sectionView];
It's a bug.
You can quickly solve it by adding, after the line:
[self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];
this lines:
UIView *sectionView = [self.tableView headerViewForSection:indexPath.section];
[self.tableView bringSubviewToFront:sectionView];
Not a solution but your code has number of issues. Who knows what happens if you fix them ;)
(1) Your cell may be nil after this line:
ListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
It should look like this:
ListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[ListCell alloc] initWithStyle:style
reuseIdentifier:cellIdentifier] autorelease];
}
(2) Two memory leaks in
-(UIView *)tableView:(UITableView *)aTableView viewForHeaderInSection:(NSInteger)section
->>Fix (When you add the label as subview it gets +1 ref).
UILabel *textLabel = [[[UILabel alloc] initWithFrame:CGRectMake(10, 0, 300, 21)] autorelease];
->>Fix (When you add the view as subview it gets +1 ref).
UIImageView *backgroundView = [[[UIImageView alloc]initWithImage:[AXThemeManager sharedTheme].tableviewSectionHeaderBackgroundImage] autorelease];
(3) Not a defect but this may help you. Try using this instead of [table reloadData]. It allows to animate things nicely and is not such a hardcore way to update the table. I'm sure it is much more lightweight. Alternatively try to look for other "update" methods. Given you don't delete rows in your example, something like [updateRowsFrom:idxFrom to:idxTo] would help.
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
float heightForHeader = 40.0;
if (scrollView.contentOffset.y<=heightForHeader&&scrollView.contentOffset.y>=0) {
scrollView.contentInset = UIEdgeInsetsMake(-scrollView.contentOffset.y, 0, 0, 0);
} else if (scrollView.contentOffset.y>=heightForHeader) {
scrollView.contentInset = UIEdgeInsetsMake(-heightForHeader, 0, 0, 0);
}
}
What I did to solve this problem was to set the zPosition of the view in the section header.
headerView.layer.zPosition = 1000 //just set a bigger number, it will show on top of all other cells.
Here is the source code:
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[self loadingWithBackground]; //this view called the spinner
return indexPath;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[self performSelectorOnMainThread:#selector(performIndexPath:)
withObject:indexPath
waitUntilDone:YES];
}
-(void)performIndexPath:(NSIndexPath *)aIndexPath{
//application logic, flip the view over, and display the data
}
But it works perfectly right:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[self loadingWithBackground];
return;
//bababbaa
}
The code of loadingWithBackground:
-(void)loadingWithBackground{
[self performSelectorOnMainThread:#selector(performLoadingWithBackground)
withObject:NULL
waitUntilDone:YES];
[m_oUIActivityIndicatorView startAnimating];
}
-(void)performLoadingWithBackground{
self.m_isLoadFinished = NO;
if(m_oUIActivityIndicatorView != NULL){
[m_oUIActivityIndicatorView removeFromSuperview];
m_oUIActivityIndicatorView = NULL;
}
if(m_oUIActivityIndicatorBackgroundView != NULL){
[m_oUIActivityIndicatorBackgroundView removeFromSuperview];
m_oUIActivityIndicatorBackgroundView = NULL;
}
m_oUIActivityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[m_oUIActivityIndicatorView setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleWhiteLarge];
m_oUIActivityIndicatorBackgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, m_oUIActivityIndicatorView.frame.size.width, m_oUIActivityIndicatorView.frame.size.height)];
[m_oUIActivityIndicatorBackgroundView setBackgroundColor:[UIColor grayColor]]; //I love the black one
[m_oUIActivityIndicatorBackgroundView setAlpha:0.8];
[m_oUIActivityIndicatorBackgroundView setCenter:CGPointMake(self.view.frame.size.width/2.0, self.view.frame.size.height/2.0)];
m_oUIActivityIndicatorBackgroundView.layer.cornerRadius = 10;
[m_oUIActivityIndicatorBackgroundView addSubview:m_oUIActivityIndicatorView];
m_oUIActivityIndicatorView.hidden = NO;
[self.view addSubview:m_oUIActivityIndicatorBackgroundView];
}
I tried to perform the loadingWithBackground using main thread, but it also can't show. When I copy the method: loadingWithBackground in the didSelectRowAtIndexPath, it doesn't shows the loadingWithBackground neither. How can I achieve it? Thanks.
NSInteger SelectedIndex;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SelectedIndex = indexPath.row;
// Start animating activity indicator
[self performSelectorOnMainThread:#selector(performIndexPath) withObject:nil waitUntilDone:NO];
// OR USING TIMER
// timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(performIndexPath) userInfo:nil repeats:NO];
}
-(void)performIndexPath {
//stop activity indicator
}
A couple of things I'd try:
Move [m_oUIActivityIndicatorView startAnimating]; to the end of the performLoadingWithBackground method and call that method instead of loadingWithBackground. Seems to be pretty much the same.
Add [self.view bringSubviewToFront:m_oUIActivityIndicatorBackgroundView]; after [self.view addSubview:m_oUIActivityIndicatorBackgroundView];. Not sure if it will help, but I always do it
Debugging
Hope it helps
I have created custom cells with a button that changes the state of a row from "availible" to "bought", and I also have an UIImageview which shows the state. I want the cell to redraw with the new state when the user presses the button.
I have been able to get the UIImage to be redrawn after the User scrolls the cell out of view. This is what I have so far:
Initialization
- (void)viewDidLoad
{
[super viewDidLoad];
self.clearsSelectionOnViewWillAppear = YES;
self.contentSizeForViewInPopover = CGSizeMake(600.0, 560.0);
//[self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
[self.tableView setRowHeight:60];
[self.tableView setBackgroundColor:[UIColor blackColor]];
}
Got only one section:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
And all items are in the "paketListe" array:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [paketListe count];
}
The cellForRow function only gets called, when the cell is out of view and when the cell is initially drawn.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"CellForRow called!");
PaketTableViewCell *cell = [[[PaketTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil] autorelease];
[cell setDelegate:self];
Paket *tempPaket = [paketListe objectAtIndex:[indexPath row]];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *userDocumentsPath = [paths objectAtIndex:0];
NSString *fileName = [NSString stringWithFormat:#"%#/%#/%#",userDocumentsPath, [tempPaket productID], [tempPaket previewPic]];
[cell setProductIndex:[indexPath row]];
//... Setting some Attributes in my custom cell, cut out for simplicity....
cell.backgroundColor = [UIColor clearColor];
return cell;
}
The code for the Button, this is a delegate call, so my custom sell calls [delegate buttonPushed:(int)];
- (void) buttonPushed:(int) myProductIndex {
NSLog(#"Pushed: %#", [[paketListe objectAtIndex:myProductIndex] productID]);
CoreDataWrapper *tempCDW = [[CoreDataWrapper alloc] init];
[tempCDW initCoreData];
Paket *tempPaket = [paketListe objectAtIndex:myProductIndex];
//Doing some work in Core Data an my "paketListe" array. Left out for simplicity...
[tempCDW release];
//Here it begins....
[[self tableView] reloadData];
[[self tableView] beginUpdates];
[[self tableView] reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:myProductIndex inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
[[self tableView] endUpdates];
}
Now I tried the reloading with different commands... Reload the whole thing with just reloadData or the begin-/endUpdate thing. Nothing refreshes the cell. After several hours of reading I think I did everything right, but no refresh.
I appreciate any help.
Quick and dirty:
- (void)configureCell:(PaketTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
//Any cell-specific code
[cell setNeedsLayout]; //or [cell setNeedsDisplay];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//Code to create any/a general cell
[self configureCell:cell];
return cell;
}
- (void) buttonPushed:(int) myProductIndex {
//Code to identify the cell in which your button is (use "super")
[self configureCell:cellForPushedButton];
}
I got to know your problem you have missed in reloadRowsAtIndexPaths "nil".
Do as i wrote below, it will work.
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:[NSIndexPath indexPathForRow:myProductIndex inSection:0], nil] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];