UITableView Selection fade away - iphone

How can I give a tableView cell background color the fade effect as soon as it appears. I know how to get a cell selected when the table view appears but the color persists for ever. I would like it to stay there for a while (2 seconds or whatever) and then fade away.
Someone said that I should use [tableView performSelector] so I wrote this:
-(void) viewdidLoad {
[tableView performSelector:#(highlight) withObject:nil afterDelay:2];
}
-(void) highlight
{
//I have a row selected as soon as my view appears
-[tableView deselectRowAtIndexPath:[tableView indexPathForSelectedRow] animated:YES];
}
But when I used it, my application crashed. I think the crashing has something to do with the "withObject" attribute.
Can anyone help me with this?

-(void)viewDidAppear:(BOOL)animated {
[self performSelector:#selector(highlight) withObject:nil afterDelay:5];
[super viewDidAppear:animated];
}
-(void)highlight{
[tableView deselectRowAtIndexPath:[tableView indexPathForSelectedRow] animated:YES];
}
That worked for me, i think you may have missed out #selector() in performSelector. Be careful with the delay as keeping table cells selected after navigating back to the view is not recommended in Apples UI guidelines (as far as i can remember).

The - highlight method belongs to your controller object, not to the table view. If you change tableView to self in the second line, it should stop the app crashing.

Related

UICollectionView Frame Wrong With Toolbar

I have a UICollectionViewController that is part of a Navigation Controller and Tab Bar Controller. At the beginning of the Navigation Controller, I have Interface Builder set to display the Toolbar at the bottom of the View. For the first View Controller of the Navigation Controller, I am using [self.navigationController setToolbarHidden:YES animated:YES]; in order to hide the Toolbar, then in my UICollectionViewController, I use
-(void)viewWillAppear:(BOOL)animated
{
[self.navigationController setToolbarHidden:NO animated:YES];
}
in order to have the Toolbar be displayed. This part works perfectly, my problem comes from the UICollectionViewCell that I have in the UICollectionView. I want it to be the full size of the UICollectionView, and I'm using AutoLayout, so I'm using:
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return self.collectionView.frame.size;
}
However, the first time the view is loaded, it returns the wrong value. The first time the view and CollectionView are loaded, that returns the size of the collectionView INCLUDING the height of the Toolbar, which then leads to the warning:
the item height must be less that the height of the UICollectionView
minus the section insets top and bottom values.
And the cell doesn't load. However, when I force a [self.collectionView reloadData]; after the warning, the correct value is returned for self.collectionView.frame.size and the cell loads.
I'm really frustrated at this and would greatly appreciate any help that anyone can offer.
Edit:
After reading Mundi's comment, I ended up changing some of my code to:
-(void)viewWillAppear:(BOOL)animated
{
[self.navigationController setToolbarHidden:NO animated:YES];
[self.collectionView reloadData];
}
And the issue was solved. However, now I have a new but related problem. When I add [self.collectionView scrollToItemAtIndexPath:selectedIndexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO]; (selectedIndexPath is pushed from the first view controller) to viewWillAppear, I get the same warning about the size of the UICollectionView item. If I have my code like this:
-(void)viewWillAppear:(BOOL)animated
{
[self.navigationController setToolbarHidden:NO animated:YES];
[self.collectionView scrollToItemAtIndexPath:selectedIndexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
[self.collectionView reloadData];
}
I don't get that size warning, but my collectionView always starts at index 0 rather than selectedIndexPath. If I change around the ordering to this:
-(void)viewWillAppear:(BOOL)animated
{
[self.navigationController setToolbarHidden:NO animated:YES];
[self.collectionView reloadData];
[self.collectionView scrollToItemAtIndexPath:selectedIndexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
}
I get that size warning, and the cell doesn't load until I invoke a method to call reloadData. However, it does start at the correct indexPath.
This problem is really about dealing with the view lifecycle. In viewWillAppear all the subviews are already laid out, so maybe you are hiding the toolbar too late.
Your solution to call reloadData after showing the toolbar is not so bad as all this happens before the view appears. It is logical - the geometry has changed and you have to recalculate the layout.
Try experimenting with the view controller's hidesBottomBarWhenPushed property. Maybe that takes care of this problem without having to code anything.

Delete button in UIViewController which is showing the detail of a cell

I have an iPhone app that has a tableview which contains cells that when touched show a detail of that object. I would like to add a Delete button to the bottom of the detail view. When the user presses it the object which is represented by the cell should be removed and the app should return to the TableView.
In terms of best practices, which is the ideal way to accomplish this?
There are few ways in which you can signal the deletion. One of them is delegates. You can define your delegate like this,
#protocol DetailViewControllerDelegate
- (void)shouldDeleteDetailObject:(id)object
#end
And then your table view controller subclass adopt the protocol and implement the method like,
- (void)shouldDeleteDetailObject:(id)object {
[self.objectsArray removeObject:object];
[self.navigationController popViewControllerAnimated:YES];
}
And then you message [self.tableView reloadData]; in viewWillAppear: as sandy has indicated.
Your button action will be implemented as,
- (IBAction)deleteObject {
if ( self.delegate && [self.delegate respondsToSelector:#selector(shouldDeleteDetailObject:)] ) {
[self.delegate shouldDeleteDetailObject:self.detailObject];
}
}
And delegate should be an assigned property.
You can also look at notifications but this is a better route for this situation.
I think there is nothing serious about this, if you successfully delete the particular details after that on backing on previous view (tableview) you just use this
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.tableView reloadData];
}

UITableView doesn't remember its scroll position

I can post code, though I'm not quite sure where to start. I have a UITableView with custom cells, in the middle tier of a three-level UINavigationView scheme. When I drill down to the bottom level and see details about my data, then use the top-left back button, my table view comes back scrolled all the way to the top, rather than scrolled down to where it was when I last saw it. Where should I look about this?
EDIT:
Here's the code in the EventsTableViewController to load an instance of EventsDetailViewController and push it on the navigation controller:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *events = [DataManager sharedDataManager].eventList;
Event *selectedEvent = [events objectAtIndex:[indexPath row]];
EventsDetailViewController *detailController = [[EventsDetailViewController alloc] initWithNibName:#"EventsDetailView" bundle:nil];
[detailController loadEvent:selectedEvent];
MyAppDelegate *del = (MyAppDelegate *)[UIApplication sharedApplication].delegate;
[del.navigationController pushViewController:detailController animated:YES];
[detailController release];
}
There's nothing in my EventsTableViewController about reloading the table, as far as I can see--I haven't implemented viewWillAppear or viewDidLoad or any of those.
Are you by any chance reloading the table where you shouldn't be? In the ViewWillAppear method for example?
I was accidentally instantiating a new copy of my table view whenever I hit this level of the navigation. That's all fixed now and this isn't a problem anymore.
I have a very similar question. When I go to the chatView, which is a UIViewController belonging to the navigationController and has a tableView as a subview, and then I scroll to some point, and then I go back, and then I go back to the chatView, it starts at the top again. I am allocing the tableView in loadView of the chatView, so the tableView gets realloced everytime I come back. But, isn't this standard? How do I just always scroll to the bottom like the iPhone Messages app and show the scroll bar for a few seconds at the bottom?
Here's the code:
http://github.com/acani/acani-chat/blob/master/Lovers2/Classes/ChatViewController.m#L184
Thanks!
Matt
are you doing:
[self presentViewController:navController animated:YES completion:nil];
where self is tableviewcontroller? give this a try:
[self.navigationController presentViewController:navController animated:YES completion:nil];

tableView frame.origin problem after textField resignFirstResponder in custom tableViewCell

I'm using an tableView with custom cells. When I want to display another view using the pushViewController function of the navigationController I loop through the textfields and call resignFirstResponder on them. But resignFirstResponder does only work when the textfields are being displayed so I scroll first to the top of the page. This is the code:
NSIndexPath *topIndexPath;
topIndexPath = [[NSIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0];
[self.tableView scrollToRowAtIndexPath:topIndexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
[[self textFieldForRow:0] resignFirstResponder];
[[self textFieldForRow:1] resignFirstResponder];
[[self textFieldForRow:2] resignFirstResponder];
[[self textFieldForRow:3] resignFirstResponder];
This works, but after this my tableView has some weird problem with its origin. I tried to set it's superviews origin to 0, but that doesn't help.
Here is a screenshot of the problem: link
As you can see, my tableview is too large and the scrollbar is stuck in the middle of the view when reaching the bottom.
Sorry for my english, I hope that you can understand me,
Thanks in advance!
Hans
It was actually quite simple. Just put your resignFirstResponder in -viewWillDisappear
edit: this is better and less hacky, I added this to my class, and it worked:
edit 2: seems that your app will be rejected when using the previous code. Here is a updated public api version:
- (void)viewWillDisappear:(BOOL)animated
{
[self.view findAndResignFirstResponder];
}
And:
#implementation UIView (FindAndResignFirstResponder)
- (BOOL)findAndResignFirstResponder
{
if (self.isFirstResponder) {
[self resignFirstResponder];
return YES;
}
for (UIView *subView in self.subviews) {
if ([subView findAndResignFirstResponder])
return YES;
}
return NO;
}
#end
(source: Get the current first responder without using a private API)
I would fix your other problem. I imagine when you say you can't call "resignFirstResponder" when the other textFields are on screen, you mean that there is a crash?
If so, it is because of screen cells don't exist and therefore the textfields are gone as well. They are recycled (so they can be dequeued for new cells).
The easy solution is to only call resignFirstResponder only on textFields that ARE on screen.
What you are doing now seems a little hacky.

How do I display a placeholder image when my UITableView has no data yet?

I have an application that, on load, displays a UITableView with the user's data in it.
However, when the user first loads the application (before they've created any data), I'd like to display, instead of an empty table, a background image (with an arrow pointing to the 'add a record' navbar button). Once the user has added their first record, the tableview is displayed instead. I've seen numerous apps do this - the only example I can think of at present is Cha-Ching, before you have any budgets/etc set up. I can't for the life of me work out how to do this, though.
I initially added a navigationcontroller to the main window's xib, the rootViewController of which was a custom viewcontroller/xib. This rootViewController contained the background image with a hidden tableview above it, and a custom tableviewcontroller that managed the tableview. This seemed to work just fine, and if there was data it would load and display in the table. However, if I was to scroll the data offscreen, the app would crash, with this error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:
'*** -[UITextEffectsWindow tableView:cellForRowAtIndexPath:]: unrecognized selector sent to instance 0xd2d130'
I have no clue what a UITextEffectsWindow is, or why it was trying to manage my tableview. I presume something may be hooked up incorrectly in my view hierarchy...
If there's a much simpler/more straightforward way of doing this, I'd be very grateful if someone could explain it. How would you do this?
Thanks in advance.
Here's one solution that I've been satisfied with so far.
First, I created a transparent view that was the same size as my TableView. I add this view as a sibling of the TableView whenever the TableView's data source has no data in it. I completely remove the view when data is available because transparency can affect the smoothness of the scrolling animation with TableViews.
I simply added a transparent label to this view that says something to the effect of "No Data Available". Adding a little shadowing to this label really helped to reinforce the concept that the label was 'floating' over top of the empty TableView.
I like your method of using an image though. Done properly, it might save you some work if you don't have to localize a string like I currently do.
To achieve this using a UITableViewController subclass as my only view (within a UINavigationController as per the Apple template) I used the following approach:
Create a UIView of the size of my tableView in the XIB that contains your UITableViewController and tableView.
Add an ImageView set with my placeholder image to the UIView.
Wire up the UIView as an IBOutlet (in the example code below, I called it emptyTableView)
When it is time to show the placeholder from within the UITableViewController subclass :
[self.tableView addSubView:emptyTableView];
[self.tableView setScrollEnabled:FALSE];
Disabling the scroll is necessary otherwise the user will be able to move the placeholder image up and down. Just remember to enable it once the user adds an item.
To remove the image view
[emptyTableView removeFromSuperview];
To do this, I use the following controller instead of UITableViewController. It will automatically place a view over the table when it is empty, and remove it when it is filled.
Just call [self reloadData] instead of [self.tableView reloadData] so that it can check if the table became empty.
In your subclass, implement a makeEmptyOverlayView function that will create the view to show over an empty table.
#interface MyTableViewController : UITableViewController
{
BOOL hasAppeared;
BOOL scrollWasEnabled;
UIView *emptyOverlay;
}
- (void) reloadData;
- (void) checkEmpty;
#end
#implementation MyTableViewController
- (void)viewWillAppear:(BOOL)animated
{
[self reloadData];
[super viewWillAppear: animated];
}
- (void)viewDidAppear:(BOOL)animated
{
hasAppeared = YES;
[super viewDidAppear: animated];
[self checkEmpty];
}
- (void)viewDidUnload
{
if (emptyOverlay)
{
self.tableView.scrollEnabled = scrollWasEnabled;
[emptyOverlay removeFromSuperview];
emptyOverlay = nil;
}
}
- (void) reloadData
{
[self.tableView reloadData];
if (hasAppeared &&
[self respondsToSelector: #selector(makeEmptyOverlayView)])
[self checkEmpty];
}
- (void) checkEmpty
{
BOOL isEmpty(YES);
id<UITableViewDataSource> src(self.tableView.dataSource);
NSInteger sections(1);
if ([src respondsToSelector: #selector(numberOfSectionsInTableView:)])
sections = [src numberOfSectionsInTableView: self.tableView];
for (int i(0); i<sections; ++i)
{
NSInteger rows([src tableView: self.tableView numberOfRowsInSection: i]);
if (rows)
isEmpty = NO;
}
if (!isEmpty != !emptyOverlay)
{
if (isEmpty)
{
scrollWasEnabled = self.tableView.scrollEnabled;
self.tableView.scrollEnabled = NO;
emptyOverlay = [self makeEmptyOverlayView];
[self.tableView addSubview: emptyOverlay];
[emptyOverlay release];
}
else
{
self.tableView.scrollEnabled = scrollWasEnabled;
[emptyOverlay removeFromSuperview];
emptyOverlay = nil;
}
}
else if (isEmpty)
{
// Make sure it is still above all siblings.
[emptyOverlay retain];
[emptyOverlay removeFromSuperview];
[self.tableView addSubview: emptyOverlay];
[emptyOverlay release];
}
}
#end
If you use Three20, you can easily set any image you want as a place holder prior to your table being populated.
So, to solve this I did as discussed in the comments above:
I created a normal UIViewController subclass, which contained a UIImageView and a UITableView. The viewController conforms to the UITableViewDelegate and UITableViewDatasource protocols, and looks after the tableView. The viewController class simply shows or hides the imageView depending on whether data is available.
I was going wrong before by trying to manage both these views with a UITableViewController. A UITableViewController has to have a tableView as its view, whereas, with this solution, a viewController can contain both the image and the tableView, and implement the necessary protocols to manage the tableView.
Thanks for all the help!