I have an issue with a View Controller which is loaded from a NIB. In this NIB I have 2 views, a landscape and portrait. When willAnimateForRotation occurs I i set
// willAnimateForRotation..
if (isLandscape) {
self.view = self.viewLandscape;
}
else {
self.view = self.portraitView;
}
in the beginning I set both tables to editing mode:
// viewDidLoad
[self.tableViewPortrait setEditing:YES animated:NO];
[self.tableViewLandscape setEditing:YES animated:NO]; // *
// *
I am pretty sure this line is causing cellForRow to be called on tableViewLandscape at this point, instead of when i set self.tableView = self.tableViewLandscape
It only happens the first time, if I perform 2 orientation changes it will load the items into the tableView correctly.
i dont want to do a [self.tableView reloadData] in orientation change.. that would be redundant.
can anyone see a more graceful way for me to fix this?
Ok, best I could come up with is:
//willAnimateToRotation..
if (!self.tableViewLandscape.editing)
{
[self.tableViewLandscape setEditing:YES animated:NO];
}
its a cheap hack but can't think of a more correct solution.
Related
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.
I have an iPhone app that uses a UINavigationController, some table views, and iAd. At the top level, my navigation controller shows the navigation bar. At lower levels, it does not.
The problem I am having is that sometimes the frame of my top level UITableView goes below the bottom of the screen. The reason it happens is this:
my viewWillAppear method looks like this:
-(void) viewWillAppear:(BOOL)animated {
[self.navigationController setNavigationBarHidden:NO animated: animated]; // changing the last animated to NO does not help.
[super viewWillAppear:animated];
}
and my viewDidLoad method looks like this:
- (void)viewDidLoad {
[self.navigationController setNavigationBarHidden:NO animated: NO];
[super viewDidLoad];
[self createTableView];
ADBannerView *abv = [[ADBannerView alloc]initWithFrame: [self initialBannerViewFrame]];
abv.delegate=self;
[self.view addSubview:abv];
self.bannerView = abv;
[self moveBannerViewOffscreen];
[abv release];
}
Lastly, moveBannerViewOffscreen looks like this:
-(void) moveBannerViewOffscreen {
// moving it down and off
CGRect newBannerFrame = self.bannerView.frame;
CGFloat screenHeight = [[UIScreen mainScreen]bounds].size.height;
newBannerFrame.origin.y=screenHeight;
bannerView.frame = newBannerFrame;
CGRect newTableFrame = self.selectionTableView.frame;
newTableFrame.size.height = self.view.bounds.size.height;
self.selectionTableView.frame = newTableFrame;
}
When the view is loading, what happens is that even though I have called
[self.navigationController setNavigationBarHidden: NO animated: NO];
the the frame of my view is not immediately adjusted to account for the navigation bar. This is still true when moveBannerViewOffscreen executes. So the height of the table view is set to 480. When the navigation bar comes in, the result is that the bottom of the table view is below the screen, and the user can't select the last row.
I'm sure I could use an NSTimer to set up some kludge to fix this. But is there a clean way to organize my code so the problem doesn't come up in the first place?
Thanks
At first glance (without fully understanding your problem, I admit) I suspect that setting yourself as the navigation controller's delegate in order to take advantage of one of these methods would help with your timing:
navigationController:didShowViewController:animated:
navigationController:willShowViewController:animated:
perhaps not moving your banner until didShowViewController has been called.
(Apologies if I didn't follow your explanation.)
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.
I have a UITableView which I would like to be displayed 100px down. For some reason this only works when animated is set to YES. Why is this?
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
/*[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]
atScrollPosition:UITableViewScrollPositionNone
animated:NO];*/
/*[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathWithIndex:1]
atScrollPosition:UITableViewScrollPositionNone
animated:NO];*/
[self.tableView setContentOffset:CGPointMake(0,100) animated:YES];
}
What if you try to use setFrame instead of setContentOffset?
You should add this code in your viewDidAppear method:
[self.tableView setFrame:CGRectMake(0, 100, 320, 380)];
Depending on the other elements you have in your view (e.g. navigation controller, tool bar etc.) you will have to adjust the 380 to something else.
Using the base Navigation-based Application that XCode supplies and giving it some table cells, it works fine with what you've given. Have you tried setting the content offset within viewDidAppear rather than viewWillAppear?
Move the "setContentOffset" to "viewDidLoad" and add "reloadData" before that:
In Swift:
func viewDidLoad() {
super.viewDidLoad()
tableView.reloadData() // important for scrolling to be possible
tableView.setContentOffset(newContentOffset), animated: false)
}
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!