I am trying to add a subview on a landscape only app in a landscape only mode. This seems to work fine on newer iPads, However when testing this on an iPad 2 the subview only seems to appear in a portrait mode. What could I be doing wrong here?
Edit: This UIView shows fine on IOS 8 and above but Does not show up properly for IOS 7.1
The Place where I add the subview:
MobileWebViewController *mobileViewController = [[MobileWebViewController alloc]
initWithNibName:#"MobileWebViewFrame"
bundle:[NSBundle mainBundle]];
[mobileViewController setUrlAddress:#"http://www.stackoverflow.com"]; //user defined function
[[UIApplication sharedApplication].keyWindow addSubview: mobileViewController.view];
Now in my MobileWebViewController I have this:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle
*)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
return self;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return [[UIApplication sharedApplication] statusBarOrientation];
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscape;
}
- (BOOL)shouldAutorotate
{
return YES;
}
//For Older versions
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (void)didRotateFromInterfaceOrientation:
(UIInterfaceOrientation)fromInterfaceOrientation
{
[UIViewController attemptRotationToDeviceOrientation];
}
- (void) setUrlAddress:(NSString *)url
{
self.urlAddress = url;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight |
UIViewAutoresizingFlexibleWidth;
[self setNeedsStatusBarAppearanceUpdate];
[[NSUserDefaults standardUserDefaults] synchronize];
self.MobileWebView.delegate = self;
self.MobileWebView.scalesPageToFit = NO;
self.MobileWebView.multipleTouchEnabled = NO;
self.MobileWebView.frame = CGRectMake(0,
self.MobileWebView.frame.origin.y,self.view.frame.size.width,
self.view.frame.size.height - self.MobileWebView.frame.origin.y);
self.MobileWebView.transform = CGAffineTransformIdentity;
}
where MobileWebView is:
#property (strong, nonatomic) IBOutlet UIWebView *MobileWebView;
from the xib file.
From what I could debug and see . The screen orientation seems to be still landscape but this particular view came in portrait possibly because of a different co ordinate system.
I realized on that before IOS 8 The values of Screen size returned did not change according to the orientation of the App but from IOS8 onwards it does.
Basically wherever I was calling CGRectMake to Create the rectangle I was using the wrong values of Height and Width to Create my webview Frames.
I now used this:
CGRect screenRect = [MobileAppHelper boundsForOrientation:[[UIApplication sharedApplication] statusBarOrientation]];
where boundsForOrientation is :
+ (CGRect) boundsForOrientation:(UIInterfaceOrientation)orientation
{
CGFloat width = [[UIScreen mainScreen] bounds].size.width;
CGFloat height = [[UIScreen mainScreen] bounds].size.height;
CGRect bounds = CGRectZero;
if (UIInterfaceOrientationIsLandscape(orientation))
{
bounds.size = CGSizeMake(MAX(width, height), MIN(width, height));
}
else
{
bounds.size = CGSizeMake(MIN(width, height), MAX(width, height));
}
return bounds;
}
This gets the new width and height of the screen according to its orientation. I use these screen height and width values whenever I am manually drawing frames and views.
I have got a UIButton inside a UIView that I set as the cameraOverlayView.
I also scaled the uiimagepicker cameraView to cover the whole screen (as you can see below in the picture).
Here's my code :
// CameraViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
if(!self.imagePickerController)
[self setupCamera];
}
-(void)setupCamera{
self.imagePickerController = [[UIImagePickerController alloc] init];
self.imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext;
self.imagePickerController.delegate = self;
self.imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
self.imagePickerController.showsCameraControls = NO;
self.imagePickerController.cameraOverlayView = self.overlayView;
[self scaleCameraPreview];
// Present picker in the next loop (to avoid warning - "Presenting view controllers on detached view controllers is discouraged")
[self performSelector:#selector(presentPicker) withObject:nil afterDelay:0.0];
}
-(void)presentPicker{
[self presentViewController:self.imagePickerController animated:NO completion:nil];
}
-(void)scaleCameraPreview{
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
int heightOffset = 0;
if(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0"))
{
heightOffset = 120;
}
float cameraAspectRatio = 4.0 / 3.0;
float imageWidth = floorf(screenSize.width * cameraAspectRatio);
float scale = ceilf(((screenSize.height + heightOffset) / imageWidth) * 10.0) / 10.0;
self.imagePickerController.cameraViewTransform = CGAffineTransformMakeScale(scale, scale);
}
-(IBAction)cameraButtonPressed:(id)sender{
[self.imagePickerController takePicture];
}
And this is my hierarchy of viewcontrollers:
- UINavigationController
- SWRevealViewController (manages a drawer functionality between different controllers)
- UINavigationController (current controller for the camera)
- CameraViewController
- PickerViewController (Presented modally as you can see above)
- PhotoTakenViewController (Controller that will fire after UIImagePicker returns an image)
EDIT: Adding overlay info
I have searched online for similar threads (e.g. UIImagePickerController Overlay Buttons Not Firing) but didn't find any solution.
The UIButton that you can see in the picture, is inside a UIView this is the overlayView in the code assigned to the cameraOverlayView. It is also connected to the cameraButtonPressed: action through an outlet, and it works perfectly if I don't add the UIImagePickerController.
It isn't responding to any touches I don't know why.
Does anyone know what's going on?
I've fixed my issue but I believe in a sketchy way:
[self presentViewController:self.imagePickerController animated:NO completion:^(void){
// Re-add the button so it is on top of the overlay
[self.imagePickerController.view addSubview:self.takePhotoButton];
}];
Will probably come back later to this issue and post an update in case I find a better solution.
Presenting a view from a UIAlertController moves the alert to a buggy position at the top-left corner of the screen. iOS 8.1, device and simulator.
We have noticed this in an app when we attempt to present a view from the current "top-most" view. If a UIAlertController happens to be the top-most view we get this behavior. We have changed our code to simply ignore UIAlertControllers, but I'm posting this in case others hit the same issue (as I couldn't find anything).
We have isolated this to a simple test project, full code at the bottom of this question.
Implement viewDidAppear: on the View Controller in a new Single View Xcode project.
Present aUIAlertController alert.
Alert controller immediately calls presentViewController:animated:completion: to display and then dismiss another view controller:
The moment the presentViewController:... animation begins, the UIAlertController is moved to the top-left corner of the screen:
When the dismissViewControllerAnimated: animation ends, the alert has been moved even further into the top-left margin of the screen:
Full code:
- (void)viewDidAppear:(BOOL)animated {
// Display a UIAlertController alert
NSString *message = #"This UIAlertController will be moved to the top of the screen if it calls `presentViewController:`";
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"UIAlertController iOS 8.1" message:message preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:#"I think that's a Bug" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
// The UIAlertController should Present and then Dismiss a view
UIViewController *viewController = [[UIViewController alloc] init];
viewController.view.backgroundColor = self.view.tintColor;
[alert presentViewController:viewController animated:YES completion:^{
dispatch_after(0, dispatch_get_main_queue(), ^{
[viewController dismissViewControllerAnimated:YES completion:nil];
});
}];
// RESULT:
// UIAlertController has been moved to the top of the screen.
// http://i.imgur.com/KtZobuK.png
}
Is there anything in the above code that would be causing this issue? Do any alternatives exist that would allow bug-free presentation of a view from a UIAlertController?
rdar://19037589
http://openradar.appspot.com/19037589
I encountered a situation where sometimes a modal view would present itself on top of a an alert (silly situation, I know), and the UIAlertController could appear in the top left (like the 2nd screenshot of the original question), and I found a one-liner solution that seems to work. For the controller that's about to be presented on the UIAlertController, change its modal presentation style like so:
viewControllerToBePresented.modalPresentationStyle = .OverFullScreen
This should be done just before you call presentViewController(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion completion: (() -> Void)?)
rdar://19037589 was closed by Apple
Apple Developer Relations | 25-Feb-2015 10:52 AM
There are no plans to address this based on the following:
This isn't supported, please avoid presenting on a UIAlertController.
We are now closing this report.
If you have questions about the resolution, or if this is still a critical issue for you, then please update your bug report with that information.
Please be sure to regularly check new Apple releases for any updates that might affect this issue.
I was having this issue as well. If I presented a view controller while a UIAlertController was presented, the alert would go to the top left.
My fix is to refresh the center of the UIAlertController's view in viewDidLayoutSubviews; achieved by subclassing UIAlertController.
class MyBetterAlertController : UIAlertController {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let screenBounds = UIScreen.mainScreen().bounds
if (preferredStyle == .ActionSheet) {
self.view.center = CGPointMake(screenBounds.size.width*0.5, screenBounds.size.height - (self.view.frame.size.height*0.5) - 8)
} else {
self.view.center = CGPointMake(screenBounds.size.width*0.5, screenBounds.size.height*0.5)
}
}
}
That is a bit disappointing... moving alerts to be UIViewControllers, but then disallowing some regular usage of them. I work on an application which did something similar -- it sometimes needs to jump to a new user context, and doing that presented a new view controller over top of whatever was there. Actually having the alerts be view controllers is almost better in this case, as they would be preserved. But we are seeing the same displacement now that we have switched to UIViewControllers.
We may have to come up with a different solution (using different windows perhaps), and maybe we avoid presenting if the top level is a UIAlertController. But, it is possible to restore the correct positioning. It may not be a good idea, because the code could break if Apple ever changes their screen positioning, but the following subclass seems to work (in iOS8) if this functionality is very much needed.
#interface MyAlertController : UIAlertController
#end
#implementation MyAlertController
/*
* UIAlertControllers (of alert type, and action sheet type on iPhones/iPods) get placed in crazy
* locations when you present a view controller over them. This attempts to restore their original placement.
*/
- (void)_my_fixupLayout
{
if (self.preferredStyle == UIAlertControllerStyleAlert && self.view.window)
{
CGRect myRect = self.view.bounds;
CGRect windowRect = [self.view convertRect:myRect toView:nil];
if (!CGRectContainsRect(self.view.window.bounds, windowRect) || CGPointEqualToPoint(windowRect.origin, CGPointZero))
{
CGPoint center = self.view.window.center;
CGPoint myCenter = [self.view.superview convertPoint:center fromView:nil];
self.view.center = myCenter;
}
}
else if (self.preferredStyle == UIAlertControllerStyleActionSheet && self.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPhone && self.view.window)
{
CGRect myRect = self.view.bounds;
CGRect windowRect = [self.view convertRect:myRect toView:nil];
if (!CGRectContainsRect(self.view.window.bounds, windowRect) || CGPointEqualToPoint(windowRect.origin, CGPointZero))
{
UIScreen *screen = self.view.window.screen;
CGFloat borderPadding = ((screen.nativeBounds.size.width / screen.nativeScale) - myRect.size.width) / 2.0f;
CGRect myFrame = self.view.frame;
CGRect superBounds = self.view.superview.bounds;
myFrame.origin.x = CGRectGetMidX(superBounds) - myFrame.size.width / 2;
myFrame.origin.y = superBounds.size.height - myFrame.size.height - borderPadding;
self.view.frame = myFrame;
}
}
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[self _my_fixupLayout];
}
#end
Apple may consider the view positioning to be private, so restoring it in this way may not be the best idea, but it works for now. It might be possible to store off the old frame in an override of -presentViewController:animated:, and simply restore that instead of re-calculating.
It's possible to swizzle UIAlertController itself to do the equivalent of the above, which would also cover UIAlertControllers presented by code you don't control, but I prefer to only use swizzles in places where it's a bug that Apple is going to fix (thus there is a time when the swizzle can be removed, and we allow existing code to "just work" without mucking it up just for a bug workaround). But if it's something that Apple is not going to fix (indicated by their reply as noted in another answer here), then it's usually safer to have a separate class to modify behavior, so you are using it only in known circumstances.
I think that you only should to categorize UIAlertController like this:
#implementation UIAlertController(UIAlertControllerExtended)
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.preferredStyle == UIAlertControllerStyleAlert) {
__weak UIAlertController *pSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHeight = screenRect.size.height;
[pSelf.view setCenter:CGPointMake(screenWidth / 2.0, screenHeight / 2.0)];
[pSelf.view setNeedsDisplay];
});
}
}
#end
I was dealing with the same problem with swift, and I fixed it by changing this:
show(chooseEmailActionSheet!, sender: self)
to this:
self.present(chooseEmailActionSheet!, animated: true, completion: nil)
I set modalPresentationStyle to .OverFullScreen
This worked for me.
In addition to Carl Lindberg's answer
There are two cases that also should be taken into account:
Device rotating
Keyboard height when there is a text field inside alert
So, the full answer that worked for me:
// fix for rotation
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[self.view setNeedsLayout];
}];
}
// fix for keyboard
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)keyboardWillShow:(NSNotification *)notification
{
NSDictionary *keyboardUserInfo = [notification userInfo];
CGSize keyboardSize = [[keyboardUserInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
self.keyboardHeight = keyboardSize.height;
[self.view setNeedsLayout];
}
- (void)keyboardWillHide:(NSNotification *)notification
{
self.keyboardHeight = 0;
[self.view setNeedsLayout];
}
// position layout fix
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self fixAlertPosition];
}
-(void)fixAlertPosition
{
if (self.preferredStyle == UIAlertControllerStyleAlert && self.view.window)
{
CGRect myRect = self.view.bounds;
CGRect windowRect = [self.view convertRect:myRect toView:nil];
if (!CGRectContainsRect(self.view.window.bounds, windowRect) || CGPointEqualToPoint(windowRect.origin, CGPointZero))
{
CGRect myFrame = self.view.frame;
CGRect superBounds = self.view.superview.bounds;
myFrame.origin.x = CGRectGetMidX(superBounds) - myFrame.size.width / 2;
myFrame.origin.y = (superBounds.size.height - myFrame.size.height - self.keyboardHeight) / 2;
self.view.frame = myFrame;
}
}
else if (self.preferredStyle == UIAlertControllerStyleActionSheet && self.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPhone && self.view.window)
{
CGRect myRect = self.view.bounds;
CGRect windowRect = [self.view convertRect:myRect toView:nil];
if (!CGRectContainsRect(self.view.window.bounds, windowRect) || CGPointEqualToPoint(windowRect.origin, CGPointZero))
{
UIScreen *screen = self.view.window.screen;
CGFloat borderPadding = ((screen.nativeBounds.size.width / screen.nativeScale) - myRect.size.width) / 2.0f;
CGRect myFrame = self.view.frame;
CGRect superBounds = self.view.superview.bounds;
myFrame.origin.x = CGRectGetMidX(superBounds) - myFrame.size.width / 2;
myFrame.origin.y = superBounds.size.height - myFrame.size.height - borderPadding;
self.view.frame = myFrame;
}
}
}
Also, if using category, then you need to store keyboard height somehow, like this:
#interface UIAlertController (Extended)
#property (nonatomic) CGFloat keyboardHeight;
#end
#implementation UIAlertController (Extended)
static char keyKeyboardHeight;
- (void) setKeyboardHeight:(CGFloat)height {
objc_setAssociatedObject (self,&keyKeyboardHeight,#(height),OBJC_ASSOCIATION_RETAIN);
}
-(CGFloat)keyboardHeight {
NSNumber *value = (id)objc_getAssociatedObject(self, &keyKeyboardHeight);
return value.floatValue;
}
#end
A quick fix is to always present the View Controller on top of a new UIWindow:
UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
window.rootViewController = [[UIViewController alloc] init];
window.windowLevel = UIWindowLevelNormal;
[window makeKeyAndVisible];
[window.rootViewController presentViewController: viewController
animated:YES
completion:nil];
EDIT: Tested in 2020, Xcode 11.2, iOS 13
If anyone still looking for a better answer to this, then here is my solution.
Use updateConstraints method to readjust the constraints.
your_alert_controller_obj.updateConstraints()
User manoj.agg posted this answer to the Open Radar bug report, but says:
Somehow I don't have enough reputation to post answers on Stackoverflow.
Posting his answer here for posterity. I have not tested/evaluated it.
Step 1:
Create a custom View Controller inheriting from UIViewController and implement UIPopoverPresentationControllerDelegate:
#interface CustomUIViewController : UIViewController<UITextFieldDelegate, UIPopoverPresentationControllerDelegate>
Step 2:
Present the view in fullscreen, making use of the presentation popover:
CustomUIViewController *viewController = [[CustomUIViewController alloc] init];
viewController.view.backgroundColor = self.view.tintColor;
viewController.modalPresentationStyle = UIModalPresentationOverFullScreen;
UIPopoverPresentationController *popController = viewController.popoverPresentationController;
popController.delegate = viewController;
[alert presentViewController:viewController animated:YES completion:^{
dispatch_after(0, dispatch_get_main_queue(), ^{
[viewController dismissViewControllerAnimated:YES completion:nil];
});
}];
I had a similar problem where a password input view needed to be displayed on top of any other View Controller, including UIAlertControllers. The above code helped me in solving the problem. Noteworthy change in iOS 8 is that UIAlertController inherits from UIViewController, which was not the case for UIAlertView.
In my case I was using func show(_ vc: UIViewController, sender: Any?) for my UIAlertController.
It used to work perfectly for my alerts but for some reason stopped working.
I replaced:
func show(_ vc: UIViewController, sender: Any?)
with:
func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil)
Now working like a charm.
var kbHeight: CGFloat = 0
override func keyboardWillShow(_ notification: Notification) {
if let userInfo = notification.userInfo {
if let keyboardSize = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
kbHeight = keyboardSize.height
self.animateTextField(up: true)
}
}
}
override func keyboardWillHide(_ notification: Notification) {
self.animateTextField(up: false)
}
func animateTextField(up: Bool) {
let movement = (up ? -kbHeight : kbHeight)
UIView.animate(withDuration: 0.3, animations: {
self.view.frame = CGRect.offsetBy(self.view.frame)(dx: 0, dy: movement)
})
}
My code to add a UIImagePickerController:
picker = [[[UIImagePickerController alloc] init] autorelease];
picker.delegate = self;
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
picker.showsCameraControls = NO;
[self displayContentController:picker inView:cameraBoxView];
[self.view sendSubviewToBack:picker.view];
- (void)displayContentController:(UIViewController *)content inView:(UIView *)v {
//Delete the previous instance of the same view controller
for (UIViewController *vc in self.childViewControllers) {
if ([vc isKindOfClass:[content class]]) {
[vc removeFromParentViewController];
}
}
[self addChildViewController:content];
content.view.frame = v.frame;
[self.view addSubview:content.view];
[content didMoveToParentViewController:self];
if ([content isKindOfClass:[CCInnerNavigationController class]]) {
innerNavigationController = (CCInnerNavigationController *)content;
}
}
I have disabled all the device orientations except of portrait. But the image from camera is rotated. How to solve this issue in both iOS 6 and 7.
P.S. I understand that there are a lot of soludions but it seems they are too old because NOTHING of them works.
When you instatiate the UIImagePickerController you must also pass it a delegate. The delegate should be conform to 2 protocols UIImagePickerControllerDelegate and UINavigationControllerDelegate. The second makes possible to add some logic for rotation at runtime, implement this methods accordingly to what you want to achieve.
– navigationControllerPreferredInterfaceOrientationForPresentation:
– navigationControllerSupportedInterfaceOrientations:
I changed the following code:
imgView.image = img;
to:
imgView.image = [UIImage imageWithCGImage:img.CGImage scale:1 orientation:UIImageOrientationRight];
And now it works in both iOS 6 and 7. I don't know how it fixes this bug but it really works. Maybe image picker corrupts the image or image view and this code fixes it by creating a new image instance.
I have a requirement to show a status bar at certain times at the bottom of my application. I can easily put this at the bottom of my application's main view, but whenever I push a view controller on top of this (either modally or not) it hides this status bar.
Is there any way I can add a status bar like this, and have it be outside the bounds of my application itself? Ideally I'd like this to work like the call-in-progress status bar on the iPhone - when this bar appears, the app is pushed down, and a call to [[UIScreen mainScreen] applicationFrame] returns the correct size (i.e. it accounts for the presence of this status bar when calculating the height available for the app).
I wanted to do this, too, so I tried View Controller Containment. I'm still trying it out, so I'm not willing to give this a ringing endorsement, but it might be something you'd want to try playing around with yourself if you're in iOS5. But it appears to give you a status bar that will appear or disappear from the bottom of the screen.
This is a view controller that will open another view controller, but if there is status text to show, it pops up from the bottom of the screen and stays there until you get rid of it. I've only done a little testing so far, but it looks like this handles pushViewController/popViewController, but maybe not modal views.
My header looks like:
// StatusBarViewController.h
//
// Created by Robert Ryan on 7/8/12.
#import <UIKit/UIKit.h>
#interface StatusBarViewController : UIViewController
#property (strong, nonatomic) UIViewController *appController;
- (void)setStatus:(NSString *)text;
#end
My implementation file (this is ARC) looks like:
// StatusBarViewController.m
//
// Created by Robert Ryan on 7/8/12.
#import "StatusBarViewController.h"
#interface StatusBarViewController ()
{
BOOL _statusHidden;
UIView *_appView;
UILabel *_statusLabel;
}
#end
#implementation StatusBarViewController
#synthesize appController = _appController;
- (void)dealloc
{
_appView = nil;
_statusLabel = nil;
[self setAppController:nil]; // usually I don't like setters in dealloc, but this does some special stuff
}
- (void)createControlsWithStatusHidden
{
// create default app view that takes up whole screen
CGRect frame = self.view.frame;
frame.origin = CGPointMake(0.0, 0.0);
_appView = [[UIView alloc] initWithFrame:frame];
_appView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_appView.clipsToBounds = YES;
[self.view addSubview:_appView];
// create status label that is just off screen below the app view
_statusLabel = [[UILabel alloc] init];
_statusLabel.font = [UIFont fontWithName:#"Helvetica-Bold" size:12.0];
_statusLabel.backgroundColor = [UIColor darkGrayColor];
_statusLabel.textColor = [UIColor whiteColor];
CGSize size = [#"Hey!" sizeWithFont:_statusLabel.font]; // test size of box with random text
_statusLabel.frame = CGRectMake(0.0, frame.size.height, frame.size.width, size.height);
_statusLabel.textAlignment = UITextAlignmentCenter;
_statusLabel.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
[self.view addSubview:_statusLabel];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self createControlsWithStatusHidden];
_statusHidden = YES;
// I'm instantiating from storyboard. If you're using NIBs, just create your controller controller using initWithNib and then set our appController accordingly.
self.appController = [self.storyboard instantiateViewControllerWithIdentifier:#"MainNavigator"];
}
- (void)setAppController:(UIViewController *)controller
{
if (controller)
{
controller.view.frame = CGRectMake(0.0, 0.0, _appView.frame.size.width, _appView.frame.size.height);
[self addChildViewController:controller];
[controller didMoveToParentViewController:self];
if (self.appController)
{
// if we have both a new controller and and old one, then let's transition, cleaning up the old one upon completion
[self transitionFromViewController:self.appController
toViewController:controller
duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionCurveEaseInOut
animations:nil
completion:^(BOOL finished){
if (self.appController)
{
[self.appController willMoveToParentViewController:nil];
[self.appController removeFromParentViewController];
}
}];
}
else
{
// if we have no previous controller (i.e. this is our first rodeo), then just add it to the view
[_appView addSubview:controller.view];
}
}
else
{
// no new controller, so we're just removing any old on if it was there
if (self.appController)
{
// if there was an old controller, remove it's view, and remove it from the view controller hierarchy
[self.appController.view removeFromSuperview];
[self.appController willMoveToParentViewController:nil];
[self.appController removeFromParentViewController];
}
}
_appController = controller;
}
- (void)hideStatusWithCompletion:(void (^)(BOOL finished))completion
{
[UIView animateWithDuration:0.25
animations:^{
CGRect labelFrame = _statusLabel.frame;
labelFrame.origin.y += labelFrame.size.height;
_statusLabel.frame = labelFrame;
CGRect appFrame = _appView.frame;
appFrame.size.height += labelFrame.size.height;
_appView.frame = appFrame;
}
completion:completion];
}
- (void)unhideStatusWithCompletion:(void (^)(BOOL finished))completion
{
[UIView animateWithDuration:0.25
animations:^{
CGRect labelFrame = _statusLabel.frame;
labelFrame.origin.y -= labelFrame.size.height;
_statusLabel.frame = labelFrame;
CGRect appFrame = _appView.frame;
appFrame.size.height -= labelFrame.size.height;
_appView.frame = appFrame;
}
completion:completion];
}
- (void)setStatus:(NSString *)text
{
BOOL hasText = (text && [text length] > 0);
if (hasText)
{
if (!_statusHidden)
{
// if we have text, but status is already shown, then hide it and unhide it with new value
[self hideStatusWithCompletion:^(BOOL finished){
_statusLabel.text = text;
[self unhideStatusWithCompletion:nil];
}];
}
else
{
// if we have text, but no status is currently shown, then just unhide it
_statusLabel.text = text;
[self unhideStatusWithCompletion:nil];
}
_statusHidden = NO;
}
else
{
if (!_statusHidden)
{
// if we don't have text, but status bar is shown, then just hide it
[self hideStatusWithCompletion:^(BOOL finished){
_statusLabel.text = text;
}];
_statusHidden = YES;
}
}
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
And then, any view controller that wants to update the status message would use a method kind of like:
- (void)setStatus:(NSString *)text
{
UIViewController *controller = [UIApplication sharedApplication].delegate.window.rootViewController;
if ([controller isKindOfClass:[StatusBarViewController class]])
{
[(StatusBarViewController *)controller setStatus:text];
}
}