iPhone -- structuring code to avoid a race condition - iphone

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.)

Related

How can I create one UIViewController with two views that display one or the other depending on button clicked

I'm quite new to iOS development and I am stuck. Currently I am using one tab controller to switch between two view controllers (list and map view). This made it easier to use storyboard to configure the look of the two views.
Now the requirements have changed and the app needs to have one view controller with a segmented control that on click, displays either the list or the map view. In order to do this, I would need to make one view controller that can display list/map view.
I understand how the segmented controller part works, but I'm just stuck on how I can go about having two views with one or the other displayed in the same area.
How can I go about having two views in one view controller (if possible, utilizing storyboard)?
Thanks in advance!
You should not have two main views in a single view controller, instead you need to create one view controller per view that you want to show. However you can certainly have multiple subviews in a single view controller, which may be what works for you.
There are a number of approaches to solve this the problem, the correct approach would be to create a container UIViewController, and add as its childs the 2 viewcontrollers you want to show, them simply set the view to the view controller you want to display, but that would probably be overly complicated since you mention you are new to iOS development.
Therefore an easy solution (not sure if you can implement this in storyboard - since I don't like it), would be to have a single view controller, with the tabs, and 2 subviews of the main view, then you can simply switch between views by doing something like this:
[self.view addSubview:view1];
//to switch
[view1 removeFromSuperview];
[self.view addSubView:view2];
alternatively, you do not really need to remove it from superview but just hide it, and then use bringSubViewToFront to show the view that you need.
If you want to use the other approach I would recommend looking for this video the WWDC 2011 video titled "Implementing UIViewController Containment". This other question should be useful to: UISegmented control with 2 views
Hope that helps.
Using storyboard api you can switch between 2 viewControllers
- (void)viewDidLoad {
[super viewDidLoad];
UIViewController *viewController = [self viewControllerForSegmentIndex:self.typeSegmentedControl.selectedSegmentIndex];
[self addChildViewController:viewController];
viewController.view.frame = self.contentView.bounds;
[self.contentView addSubview:viewController.view];
self.currentViewController = viewController;
}
- (UIViewController *)viewControllerForSegmentIndex:(NSInteger)index {
UIViewController *viewController;
switch (index) {
case 0:
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"FirstViewController"];
break;
case 1:
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
break;
}
return viewController;
}
- (IBAction)segmentChanged:(UISegmentedControl *)sender {
UIViewController *viewController = [self viewControllerForSegmentIndex:sender.selectedSegmentIndex];
[self addChildViewController:viewController];
[self transitionFromViewController:self.currentViewController toViewController:viewController duration:0.0 options:UIViewAnimationOptionTransitionNone animations:^{
[self.currentViewController.view removeFromSuperview];
viewController.view.frame = self.contentView.bounds;
[self.contentView addSubview:viewController.view];
} completion:^(BOOL finished) {
[viewController didMoveToParentViewController:self];
[self.currentViewController removeFromParentViewController];
self.currentViewController = viewController ;
}];
self.navigationItem.title = viewController.title;
}
This is in reference to iOS tutorial by Raywenderlich. Hope this helps
With Storyboard it is possible in this way.
Create UIViewController with UISegmentControl and UITableView+UITableViewCell added to it.
Now you want to add MKMapView as well, hoverer, if you simply try to place the MapView on the ViewController, it will be added as new TableView cell, which is not what we want.
That's why you should not do it so. Instead, MapView has to be added to Storyboard's List of ViewControllers
Adjust the size and origin of MapView to be the same as TableView ones.
Now, setHidden to YES for either TableView of MapView, create and synthesize outlets for them. Then in Segment control Value Changed method implement switching:
- (IBAction)switchView:(id)sender {
self.theTableView.hidden = !self.theTableView.hidden;
self.theMapView.hidden = !self.theMapView.hidden;
if (!self.theTableView.hidden) {
[self.theTableView reloadData];
}
}

Show tab bar after its hidden

Is there any way to show a tab bar after it has been hidden?
Got a tabbar-nav structure. For one of the tabs, I need to hide the tab bar for its 2nd and 3rd level view. But at the same time I will need to show its 1st and 4th view.
The sample code from Elements isn't really applicable here I think.
I've found quite a good pragmatic solution to this problem - make the UITabBarController's view larger than it needs to be, so that the actual UITabBar is clipped by the screen.
Assuming that the tab bar view normally fills its superview, this sort of thing should work:
CGRect frame = self.tabBarController.view.superview.frame;
if (isHidden)
{
CGFloat offset = self.tabBarController.tabBar.frame.size.height;
frame.size.height += offset;
}
self.tabBarController.view.frame = frame;
The tab bar is still showing, but it's off the bottom of the screen, so appears to have been hidden.
It might have performance implications if it causes extra clipping, but so far, it seems to work.
The UIViewControllers that are pushed onto the navigation stack can do the something like the following:
- (void)viewWillAppear:(BOOL)animated {
self.tabBarController.tabBar.hidden = NO; // Or YES as desired.
}
EDIT: Added additional code below to deal with the frame. Don't think I particular recommend this idea since it relies on the internal default view structure of a UITabBarController.
Define the following category on UITabBarController:
#interface UITabBarController (Extras)
- (void)showTabBar:(BOOL)show;
#end
#implementation UITabBarController (Extras)
- (void)showTabBar:(BOOL)show {
UITabBar* tabBar = self.tabBar;
if (show != tabBar.hidden)
return;
// This relies on the fact that the content view is the first subview
// in a UITabBarController's normal view, and so is fragile in the face
// of updates to UIKit.
UIView* subview = [self.view.subviews objectAtIndex:0];
CGRect frame = subview.frame;
if (show) {
frame.size.height -= tabBar.frame.size.height;
} else {
frame.size.height += tabBar.frame.size.height;
}
subview.frame = frame;
tabBar.hidden = !show;
}
#end
Then, instead of using the tabBar.hidden change I originally suggested, do the following:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.tabBarController showTabBar:NO];
}
Obviously making sure that the implementation has included the category definition so that 'showTabBar' is known.
You need to implement a delegate method
- (BOOL)tabBarController:(UITabBarController *)tabBarController2 shouldSelectViewController:(UIViewController *)viewController
Inside that you can check which index is selected and show the tab bar
if([[tabBarController.viewControllers objectAtIndex:0] isEqual:viewController])// it is first tab
{
tabBarController.tabBar.hidden = FALSE;
}
I know this is an old post but i think the below code would help to hide the tabbar on the viewcontroller you don't want it on and has the added benefit of automatically readding the tabbar when you come back from that view controller
UIViewController *hideTabbarViewController = [[UIViewController alloc] init];
hideTabbarViewController.hidesBottomBarWhenPushed = YES;
[[self navigationController] hideTabbarViewController animated:YES];

Transparent Background with a Modal UIViewController

I have a dilema, I want to present to the user a semi-transparent view.
I found out by experimenting that if I simply pushed the transparent view to the top of my NavigationController's stack, that it would not render the transparency level I wanted. So I decided to simply add the view as a subview of the current view at the top of the stack.
This solution works, the view below is still visible, and the View is 'semi-modal'. The problem is, if the parent view inherits from UITableViewController (as mine does), then the view I 'push' onto it, does not cover the navigation bar at the top.
I really don't want to get into a situation where I am forced to enable / disable controls on the navigation bar every time I push this view, so I was wondering, if anyone knew of any solutions that I could use so that the view I push onto the UITableViewController will actually 'push over' the navigation bar?
Funny, I was just doing the same thing yesterday. Unfortunately it seems to be impossible. Once the modal view controller is in place, the previous view becomes hidden.
See this previous question on the topic.
You can still use the view controller and NIB files you have set up - here's my sample code
- (void)showUpgrade {
[self.upgradeVC viewWillAppear:NO];
[self.view addSubview:self.upgradeVC.view];
[self.upgradeVC viewDidAppear:NO];
}
- (void)hideUpgrade {
[self.upgradeVC viewWillDisappear:NO];
[self.upgradeVC.view removeFromSuperview];
[self.upgradeVC viewDidDisappear:NO];
}
- (UpgradeViewController *)upgradeVC {
if (_upgradeVC == nil) {
_upgradeVC = [[UpgradeViewController alloc] initWithNibName:[NSString stringWithFormat:#"UpgradeView_%#", self.deviceType] bundle:nil];
_upgradeVC.delegate = self;
}
return _upgradeVC;
}
You will need to store a reference to the parent view controller in the modal view controller so that you can access the -hide method. I did this through a delegate.
It would also be easy to add some animation to -show and -hide if you want it to animate up from the bottom of the screen - I was just too lazy to do this.
iOS 8 added the UIModalPresentationOverFullScreen presentation style. Set this as the presented view controller’s modalPresentationStyle. For more advanced needs, look into creating a custom presentation controller.
There is now a way to achieve this using iOS7 custom transitions :
MyController * controller = [MyController new];
[controller setTransitioningDelegate:self.transitionController];
controller.modalPresentationStyle = UIModalPresentationCustom;
[self controller animated:YES completion:nil];
To create your custom transition, you need 2 things :
A TransitionDelegate object (implementing
<UIViewControllerTransitionDelegate>)
An "AnimatedTransitioning" object
(implementing <UIViewControllerAnimatedTransitioning>)
You can find more informations on custom transitions in this tutorial : http://www.doubleencore.com/2013/09/ios-7-custom-transitions/
Try this:
ViewController *vc = [[ViewController alloc] init];
[vc setModalPresentationStyle:UIModalPresentationOverCurrentContext];
[self presentViewController:vc animated:YES completion:nil];
Have you tried looping over the Modal View Controller's subviews and setting the background color to clear for every view? This is a DFS recursive function.
- (void)setBackgroundToClearForView:(UIView *)view {
if ([view subviews]) {
for (UIView *subView in [view subviews]) {
[self setBackgroundToClearForView:subView];
}
}
if ([view respondsToSelector:#selector(setBackgroundColor:)]) {
[view performSelector:#selector(setBackgroundColor:)
withObject:[UIColor clearColor]];
}
}
To use it call:
[self setBackgroundToClearForView:self.view];
in viewDidLoad.
This will do the trick.. Try this one.
// for clear color or you can easily adjust the alpha here
YourVC *vc=[[YourVC alloc]initWithNibName:#"YourVC" bundle:nil] ;
vc.view.backgroundColor = [UIColor clearColor];
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:vc animated:NO completion:nil];
So that the view will be full screen unlike UIModalPresentationFormSheet..

Animation issue when hiding iPhone toolbar

I have a toolbar in my RootViewController and I then hide the toolbar in a SubViewController using the following code:
RootViewController
- (void)viewDidLoad {
...
[self.navigationController setToolbarHidden:FALSE animated:FALSE];
...
}
- (void)viewDidAppear:(BOOL)animated {
[self.navigationController setToolbarHidden:FALSE animated:TRUE];
[super viewDidAppear:animated];
}
SubViewController
- (void)viewDidLoad {
...
[self.navigationController setToolbarHidden:YES animated:YES];
[super viewDidLoad];
}
This all works as expected i.e. the toolbar will be hidden and unhidden using a nice vertical animation when moving from one view to another and back again.
However, there appears to be a nasty animation issue when moving from the RootViewController to the SubViewController. As the toolbar is being hidden, a white bar will appear where the toolbar was, and then quickly disappears across the screen from right to left.
Hopefully I've explained this well enough for you to understand.
Any ideas how to fix this?
Have you tried doing the animation in SubViewController's -viewWillAppear: method? You may have better luck there.
I have seen this problem a couple of times and I have found that putting the call to setToolbarHidden:animated: in the viewWillAppear: method does not always give a smooth animation with no white rectangle artifacts.
What does always work is to put the setToolbarHidden:animated: call in the viewDidAppear: method. This means that the toolbar hiding animation is triggered after the navigation controller has finished pushing the new view onto the stack, so no white rectangles. However, it also means that the whole animation is in two stages: the first animates the view in, the second hides the toolbar, so you have the appearance of a "delayed" toolbar hide. I acknowledge that this isn't always what you want.
TRY THIS
- (IBAction)hideTheToolBar:(id)sender{
//[toolBar setHidden:YES];
if (toolBar.hidden == NO)
{
[UIView animateWithDuration:0.25 delay:0.0
options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction
animations:^(void)
{
toolBar.alpha = 0.0f;
}
completion:^(BOOL finished)
{
toolBar.hidden = YES;
}
];
}
}
You can (probably should) do this in the subview controller's designated initializer, e.g. initWithNibName:bundle:
I have found very useful to set the hidesBottomBarWhenPushed property in the init of your view controller.
For instance:
- (id)init
{
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
// Custom initialization
self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.hidesBottomBarWhenPushed = YES;
}
return self;
}
It hides those spurious toolbars that appear in the push and pop transitions. Also, it frees you from manually hiding the toolbar in the ViewWillAppear method or similar approaches.

How does presentModalViewController interact with nested UITabBarController and UINavigationController

I have a view that I want to take up the full screen, so I override the init method, and some of the view methods:
- (id) init {
if (self = [super init]) {
self.wantsFullScreenLayout = YES;
}
return self;
}
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:YES];
}
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[UIApplication sharedApplication] setStatusBarHidden:NO animated:YES];
}
Now, from another screen, I want to display it as a modal view:
UIViewController *screen = [[MyScreen alloc] init];
[self presentModalViewController:screen];
[screen release];
All pretty standard stuff. When I want the full-screen view to go away, however, the previous view is shifted or stretched up by about 40 pixels.
Specificially, I have a UITabBarController with a UINavigationController inside, displaying a UITableViewController, which is the view that displays the subview, and also the view that gets shifted up. If the table is not in a navigation controller, everything works just fine, nothing gets shifted up at all. If I experiment with commenting out the wantsFullScreenLayout and setStatusBarHidden lines with no navigation bar, it sometimes shifts up just 20 pixels, or doesn't actually display on the full screen (but later it does without changing any code), or sometimes doesn't break at all (but I am not getting the full full screen with any of these)
What am I doing wrong?
Through some combination of Sean's suggestion and jumping up the responder chain, I've found a solution that works is what seems like all circumstances (so far).
First issue:
The Table View by itself does not display in a navigation controller, but may show up in one if being selected from the more view in the tab bar, and that's the case where displaying the modal view in full screen causes the table to underlap the navigation bar upon return.
Second issue:
When not displayed in a navigation controller, presenting the modal view does not take up the full screen (even though wantsFullScreenLayout is set to YES). When returning from this view, the view is shifted up by 20 pixels and you can see a gap between the bottom of the table and the top of the tab bar.
Solution:
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[self.navigationController setNavigationBarHidden:NO animated:NO];
}
- (void) presentModalViewController:(UIViewController *)screen animated:(BOOL)animated {
UIResponder *responder = self;
while (responder && ![responder isKindOfClass:[UITabBarController class]]) {
responder = [responder nextResponder];
}
[(UIViewController *)responder presentModalViewController:screen animated:YES];
}
The toggling of the navigation bar's visibility forces the relayout. Overriding presentModalViewController actually calls presentModalViewController on the tab bar controller instead, which then causes it to show in the full screen. For some reason, self.tabBarController is nil when not in the more view controller, so I had to jump up the responder chain to find it.
Your UINavigationController will get called with the viewWillAppear before the modal view is dismissed. Have you tried calling [[UIApplication sharedApplication] setStatusBarHidden:NO animated:NO]; inside the controllers that can be visible post modal dismissal. I have run into tons of problems displaying modal views on top of UINavigationControllers when bounds change. It fights any layout changes and requires lots of resetting to previous states to get it behaving nicely. It might also not hurt to call [self.navigationController setNavigationBarHidden:NO animated:NO] as well to force layout.
If this works well it might serve you to create a simple baseclass that sets these in it's viewWillAppear and then just subclass it for all non modal view controllers.
If this doesn't work you might try placing a swap view at the top level that contains the tab bar controller and then you could remove the tab bar controller with a transition when you present your modal view. Yes this isn't technically modal but would still look nice and offer the same effect. At that time since the view controller is out of the view hierarchy it shouldn't get it's layout all munged.
I think this has to do with the timing of the presentModalViewController: call. As a test you could try adding sleep(3) before you call that method. If that fixes anything, or even if it doesn't i guess I would try moving the order of things around. maybe viewDidDisappear and viewDidAppear instead of 'Will'