Resize UIWebView on webViewDidFinishDownload dilemma - ios

Suppose that I have a UIWebView that's stacked against a UIImageView (or some other view):
I want to resize my UIWebView so that I know where to put the UIImageView. The way I do it is to wait for the UIWebView to finish loading:
webViewCalculating = YES;
while (webViewCalculating == YES) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
Then I resize it when the UIWebView has finished loading, according to this suggestion:
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
CGRect frame = webView.frame;
frame.size.height = 1;
webView.frame = frame;
CGSize fittingSize = [webView sizeThatFits:CGSizeZero];
frame.size = fittingSize;
webView.frame = frame;
heightSoFar += webView.frame.size.height;
webViewCalculating = NO;
}
After this point, I'd know how tall my UIWebView is, and I can place my UIImageView accordingly. However when I push another view controller and came back to this, my hacky [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode doesn't seem to wait until my webView finished calculating the height first. Is there a better way for me to know the size of my UIWebView without waiting for it to load? Maybe something similar to sizeWithFont method of NSString?

You should not be doing your while loop that is pumping the run-loop. This is a very bad idea. You should have your scroll view container respond and relayout the subviews when the webview finishes loading. Start with a "reasonably sized webview area". Maybe show a spinner in pace while the web content is loading.
A technique I've used to do exactly this is as follows:
Firstly add some category methods to UIWebView as follows:
#interface UIWebView (ContentSize)
- (CGFloat)documentOffsetHeight;
#end
#implementation UIWebView (ContentSize)
- (CGFloat)documentOffsetHeight
{
return [[self stringByEvaluatingJavaScriptFromString:#"document.documentElement.offsetHeight"] floatValue];
}
#end
Then I've written a UIView subclass that contains the UIWebView. Eg:
#interface MyWebView : UIView <UIWebViewDelegate>
#end
#implementation MyWebView
{
BOOL _loaded;
UYIWebView *_webView;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
_webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, 1)];
_webView.delegate = self;
_webView.alpha = 0.0f;
[self addSubview:_webView]
}
}
- (CGSize)sizeThatFits:(__unused CGSize)size
{
if (_loaded)
{
CGFloat height = [webView_ documentOffsetHeight];
CGFloat width = self.frame.size.width;
return CGSizeMake(width, height);
}
return self.frame.size;
}
- (void)sizeToFit
{
CGSize fittingSize = [self sizeThatFits:CGSizeZero];
self.bounds = CGRectMake(0, 0, fittingSize.width, fittingSize.height);
}
- (void)layoutSubviews
{
[super layoutSubviews];
_webView.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
}
- (void)loadHTMLString:(NSString *)htmlString baseURL:(NSURL *)baseURL
{
// This code assumes jQuery is already used in the HTML content. If not, added it as a script resource here too.
NSString *scriptTag = #"<script type=\"text/javascript\" >jQuery(document).ready(function() { window.location = 'x-webview://ready'; });</script>\n";
NSString *const headOpeningTag = #"<head>";
if ([htmlString rangeOfString:headOpeningTag].location != NSNotFound )
{
htmlString = [htmlString stringByReplacingOccurrencesOfString:headOpeningTag
withString:[headOpeningTag stringByAppendingString:headContent]
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [htmlString length])];
}
else
{
htmlString = [headContent stringByAppendingString:htmlString];
}
[_webView loadHTMLString:htmlString baseURL:baseURL];
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.scheme isEqualToString:#"x-webview"] && [request.URL.host isEqualToString:#"ready"])
{
_loaded = YES;
[UIView animateWithDuration:0.1
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{ webView.alpha = 1.0f; }
completion:nil];
[self sizeToFit];
return NO;
}
return YES;
}
#end
So basically what this UIView subclass does is embed an invisible _webview. Then when the document has loaded and rendered the jQuery based JavaScript tries to navigate away from the current page using a custom URL scheme. This navigation is trapped an denied. But in response the view sizes to fit and gets the proper size from the HTML document.
Note that you don't have to use jQuery. You can add a DOM JavaScript event. I'm just more familiar with how jQuery does things that raw DOM events.
In your case you would have to communicate to the scrollview that the content has finished loading and that the scroll view views should re-layout. You can do this with a delegate protocol or something similar. Or maybe the scrollview is the "MyWebView" container UIView subclass. Adapt as needed.

properties are not reset, so when you come back to this controller you need to webViewCalculating = YES;
so that it doesn't instantly fail the while loop.
If you are already doing that you could try putting
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
with in the
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
}
delegate method
Hope this is useful.

Related

Adding header view to WKWebView ScrollView

Has anyone managed to successfully add a header or footer view to a WKWebView ScrollView?
I'm currently trying to do this using the method described here for a UIWebView Adding a header view to a UIWebView similar to Safari and Articles.
When this method is used in a WKWebView the content view origin.y is correctly changed but content is cut off at the bottom.
Using the scroll view content offset is also not possible as it breaks fixed positioned CSS elements in the web view.
In webView Delegate method
- (void)webViewDidFinishLoad:(UIWebView *)webView
add the following codebase,
mainWebViewObj.scrollView.contentInset = UIEdgeInsetsMake(headerView.frame.size.height,0.0,headerView.frame.size.height,0.0);
mainWebViewObj.scrollView.backgroundColor = [UIColor whiteColor];
if(![headerView superview])
{
[webView.scrollView addSubview:headerView];
[webView.scrollView bringSubviewToFront:headerView];
}
[mainWebViewObj.scrollView setContentOffset:
CGPointMake(0, -mainWebViewObj.scrollView.contentInset.top) animated:NO];
this worked perfect for me. Hope it solves your problem.
Here's an example that I think does as you describe. It offsets the web content by setting contentInset on the scrollView, and by offsetting the header view frame by a negative amount:
#implementation ViewController
{
WKWebView* _webView;
UIView* _headerView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
_webView = [[WKWebView alloc] initWithFrame: self.view.bounds];
[self.view addSubview: _webView];
[_webView loadRequest: [NSURLRequest requestWithURL: [NSURL URLWithString: #"http://www.stackoverflow.com"]]];
[_webView.scrollView setContentInset: UIEdgeInsetsMake(100, 0, 0, 0)];
_headerView = [[UIView alloc] initWithFrame: CGRectMake(0, -100, 375, 100)];
_headerView.backgroundColor = [UIColor redColor];
[_webView.scrollView addSubview: _headerView];
}
- (void) viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
_webView.frame = self.view.bounds;
CGRect f = _headerView.frame;
f.size.width = _webView.bounds.size.width;
_headerView.frame = f;
}

WebView height not set based on its content

I know this question was posted so many times but still I can not change the height of UIWebView based on its content
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.title=#"webview";
[_webView setDelegate:self];
NSString * string = #"<h1>This is HTML string</h1>";
[_webView loadHTMLString:string baseURL:nil];
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
return YES;
}
- (void)webViewDidStartLoad:(UIWebView *)webView
{
NSLog(#"start loadinng.......");
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSLog(#"finish loading.......");
CGRect frame = _webView.frame;
frame.size.height = 1;
_webView.frame = frame;
CGSize fittingSize = [_webView sizeThatFits:CGSizeZero];
frame.size = fittingSize;
_webView.frame = frame;
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
NSLog(#"Failed to load with error :%#",[error debugDescription]);
}
can anybody help me how can resize my UIWebView height?
Here is your answer.
- (void)webViewDidFinishLoad:(UIWebView *)webView {
if (webView.isLoading){
return;
}
CGSize contentSize = webView.scrollView.contentSize;
NSLog (#"height: %f",contentSize.height);
}
After gettting this height reframe your webView with new width and height and also reframe its parent view if there any.
How about this.
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[webView sizeToFit];
webView.frame = CGRectMake(x,y,width,webView.frame.size.height);
}
I usually use these methods, to set UIWebview frame as it's content size:
- (void)webViewDidStartLoad:(UIWebView *)webView
{
CGRect frame = webView.frame;
frame.size.height = 5.0f;
webView.frame = frame;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
CGSize mWebViewTextSize = [webView sizeThatFits:CGSizeMake(1.0f, 1.0f)]; // Pass about any size
CGRect mWebViewFrame = webView.frame;
mWebViewFrame.size.height = mWebViewTextSize.height;
webView.frame = mWebViewFrame;
//Disable bouncing in webview
for (id subview in webView.subviews)
{
if ([[subview class] isSubclassOfClass: [UIScrollView class]])
{
[subview setBounces:NO];
}
}
}
They are automatically called (if you set webviews delegate to this class), when WebView has finished loading it's content.
or
check this
raywenderlich-tutorial
This might helps you :)
First calculate height of web view for desired content.
NSString* lString = #"<h1>This is HTML string</h1>";
NSAttributedString* lAttributedText = [[NSAttributedString alloc] initWithData: [lString dataUsingEncoding:NSUTF8StringEncoding]
options:#{
NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: #(NSUTF8StringEncoding)
}
documentAttributes: nil
error:nil];
CGRect lBoundingRect = [lAttributedText boundingRectWithSize: CGSizeMake(_webView.bounds.size.width, CGFLOAT_MAX) options: NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context: nil];
then set it to _webView.frame
Now, load web view
[_webView setDelegate:self];
[_webView loadHTMLString: lString baseURL:nil];

Animate UITextView resize using autolayout

I'm implementing an autogrowing UITextView. I'm aiming for a similar behaviour of the message box in Whatsapp, which autogrows when your text has more than 1 line.
I'm using the approach described below which stores the height constraint in a UITextView subclass and modifies it when the text changes.
My solution animates correctly when I press the enter key inside the TextView, but it doesn't work when my typing goes over the end of the line. In this case it just changes size instantly.
Performing the animation on the delegate's - (void)textViewDidChange:(UITextView *)textView method produces the same result.
How can I correctly animate the TextView height using the auto layout system?
I'm implementing it like this:
#interface OEAutoGrowingTextView ()
#property (strong, nonatomic) NSLayoutConstraint *heightConstraint;
#end
#implementation OEAutoGrowingTextView
- (id)initWithFrame:(CGRect)frame
{
if ( !(self = [super initWithFrame:frame]) )
{
return nil;
}
[self commonInit];
return self;
}
- (void)awakeFromNib
{
[self commonInit];
}
- (void)commonInit
{
// If we are using auto layouts, than get a handler to the height constraint.
for (NSLayoutConstraint *constraint in self.constraints)
{
if (constraint.firstAttribute == NSLayoutAttributeHeight)
{
self.heightConstraint = constraint;
break;
}
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textDidChange:) name:UITextViewTextDidChangeNotification object:self];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)textDidChange:(NSNotification *)notification
{
self.heightConstraint.constant = self.contentSize.height;
[UIView animateWithDuration:1.0f animations:^
{
[self layoutIfNeeded];
}];
}
#end
Note: doing the following doesn't help.
- (void)textDidChange:(NSNotification *)notification
{
self.heightConstraint.constant = self.contentSize.height;
[UIView animateWithDuration:1.0f animations:^
{
[self layoutIfNeeded];
for (UIView *view in self.subviews)
{
[view layoutIfNeeded];
}
}];
}
Further update: This seems to be a bug in iOS 7.x, I think it's fixed on iOS 8.0.
I tried wrapping the heightConstraint change in a UIView animation block and that didn't work
That isn't how you animate a constraint change. You do it by changing the constraint and then animating the act of layout itself, like this:
// change the text view constraint here
[UIView animateWithDuration:duration animations:^{
[self.textView layoutIfNeeded];
}];
Ok, the issue is that as of ios7, .contentSize isn't correct for UITextViews. I have this functionality, and you need to compute the contentSize yourself. I added a category method to UITextView, -contentHeight, and use that instead to compute the contentSize.
See these two links.
UITextView Content Size
SO on the same question
Here is the code that fixes it:
#implementation UITextView (Sizing)
- (CGFloat)contentHeight {
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1) {
// This is the code for iOS 7. contentSize no longer returns the correct value, so
// we have to calculate it.
//
// This is partly borrowed from HPGrowingTextView, but I've replaced the
// magic fudge factors with the calculated values (having worked out where
// they came from)
CGRect frame = self.bounds;
// Take account of the padding added around the text.
UIEdgeInsets textContainerInsets = self.textContainerInset;
UIEdgeInsets contentInsets = self.contentInset;
CGFloat leftRightPadding = textContainerInsets.left + textContainerInsets.right + self.textContainer.lineFragmentPadding * 2;
leftRightPadding += contentInsets.left + contentInsets.right;
CGFloat topBottomPadding = textContainerInsets.top + textContainerInsets.bottom + contentInsets.top + contentInsets.bottom;
frame.size.width -= leftRightPadding;
frame.size.height -= topBottomPadding;
NSString* textToMeasure = self.text;
if(![textToMeasure isNotEmpty])
textToMeasure = #"-";
if ([textToMeasure hasSuffix:#"\n"]) {
textToMeasure = [NSString stringWithFormat:#"%#-", self.text];
}
// NSString class method: boundingRectWithSize:options:attributes:context is
// available only on ios7.0 sdk.
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
NSDictionary* attributes = #{NSFontAttributeName : self.font,
NSParagraphStyleAttributeName : paragraphStyle};
CGRect size = [textToMeasure boundingRectWithSize:CGSizeMake(CGRectGetWidth(frame), MAXFLOAT)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:attributes
context:nil];
CGFloat measuredHeight = ceilf(CGRectGetHeight(size) + topBottomPadding);
return measuredHeight;
} else {
return self.contentSize.height;
}
}
#end
Instead of contentSize, use this to compute the content height. You also don't need the animate at all - mine just computes and that is smooth enough, so you should make sure you really need the animation.

ios: <Error>: CGAffineTransformInvert: singular matrix

Any reason for this error "CGAffineTransformInvert"
Should I be worried?
I have a .xib with a view, and 4 webViews located outside of the view but within the same xib. Then in the code I add the webViews as subviews to a scroll view inside the view. Would that cause the problem?
Code is below:
//Called first to initialize this class. Also, initializes the nib file and tab bar name.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = NSLocalizedString(#"More", #"More");
self.tabBarItem.image = [UIImage imageNamed:#"first"];
}
return self;
}
//Initialize the more tab titles and views
-(void)initViewsandTitles{
MoreTabPages = [NSArray arrayWithObjects:self.aboutWebView,
self.newsUpdateWebView,
self.feedbackWebView,
self.creditsResourceWebView, nil];
titles = [[NSArray alloc] initWithObjects:#"About Locavore",
#"News and Updates",
#"Feedback",
#"Credits and Resources", nil];
}
//Initialize the URLs
-(void)initURLs{
websites = [[NSArray alloc] initWithObjects:#"http://www.getlocavore.com/",
#"http://twitter.com/enjoy_locavore",
#"https://getsatisfaction.com/localdirt/products/localdirt_locavore",
#"http://www.getlocavore.com/about", nil];
}
//Called after the controller's view is loaded into memory.
- (void)viewDidLoad
{
[super viewDidLoad]; //Call the super class init method
[self setupSpinner]; //Start the spinner animatio
[self initViewsandTitles]; //Initialize the views and titles
[self initURLs]; //Initialize the URLs
[self setScrollandPageViewProperties]; //Set the scroll and page view properties
[self setUpPageViews]; //Create the web pages
}
//UIScrollViewDelegate Protocol Reference. Called whn the user scrolls the content within the reciever
- (void)scrollViewDidScroll:(UIScrollView *)sender {
if (!pageControlBeingUsed) {
// Switch the indicator when more than 50% of the previous/next page is visible
CGFloat pageWidth = self.MoreTabScrollView.frame.size.width;
int page = floor((self.MoreTabScrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
self.MoreTabPageControl.currentPage = page;
self.MoreTabTitle.text = [titles objectAtIndex:page];
}
}
//UIScrollViewDelegate Protocol Reference. Called when the scroll view is about to start scolling content
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
pageControlBeingUsed = NO;
}
//UIScrollViewDelegate Protocol Reference. Called when the scroll view has ended decelerating the scrolling movement
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSLog(#"DID END SCROLLING");
pageControlBeingUsed = NO;
}
//Called when the page control value changes
- (IBAction)MoreTabChangePage {
// Update the scroll view to the appropriate page
CGRect frame;
frame.origin.x = self.MoreTabScrollView.frame.size.width * self.MoreTabPageControl.currentPage;
frame.origin.y = 0;
frame.size = self.MoreTabScrollView.frame.size;
[self.MoreTabScrollView scrollRectToVisible:frame animated:YES];
self.MoreTabTitle.text = [titles objectAtIndex:self.MoreTabPageControl.currentPage];
// Keep track of when scrolls happen in response to the page control
// value changing. If we don't do this, a noticeable "flashing" occurs
// as the the scroll delegate will temporarily switch back the page
// number.
pageControlBeingUsed=YES;
}
//Create a frame for each page and add the page to the scroll view
-(void)setUpPageViews{
//Set up all page views for the more tab
for (int i = 0; i < MoreTabPages.count; i++) {
//Get the current table view controller page
UIWebView *webController= [MoreTabPages objectAtIndex:i];
//Request the URL and load the request
NSURL *urll =[NSURL URLWithString:[websites objectAtIndex:i]];
//Run requests in seperate thread
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
NSURLRequest *firstReq = [NSURLRequest requestWithURL:urll];
[webController loadRequest:firstReq];
dispatch_sync(dispatch_get_main_queue(), ^{
//Create a frame for the current table view controller
CGRect frame = webController.frame;
frame.origin.x = self.MoreTabScrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.MoreTabScrollView.frame.size;
webController.frame = frame;
//Add the the current table view controller page to the scroll view
[self.MoreTabScrollView addSubview:webController];
//Release the controller object it is no longer needed
[webController release];
if(i == 3){
[spinner stopAnimating];
}
});
});
}
}
//Set al the properties for the scroll view and page controll
-(void)setScrollandPageViewProperties{
self.MoreTabScrollView.contentSize = CGSizeMake(self.MoreTabScrollView.frame.size.width * MoreTabPages.count,
self.MoreTabScrollView.frame.size.height);
self.MoreTabScrollView.scrollsToTop = NO;
self.MoreTabScrollView.contentOffset = CGPointMake(self.MoreTabScrollView.frame.size.width, 0);
self.MoreTabPageControl.numberOfPages = MoreTabPages.count;
}
-(void)setupSpinner{
spinner.hidesWhenStopped = YES;
[spinner startAnimating];
}
//Called if the application receives a memory warning
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//Called when the UIViewController's reference count goes to zero
- (void)dealloc {
[super dealloc];
[MoreTabPageControl release];
[MoreTabScrollView release];
[MoreTabTitle release];
[MoreTabPages release];
[titles release];
[websites release];
[spinner release];
}
#end
try setting the minimum zoom scale for your each webview.
[self.aboutWebView.scrollView setMinimumZoomScale:0.1]
it will throw the same error if the scrollview reaches zero at zero zoom.
This may happen with affine transformations when you're scaling UIScrollView instance to 0 either using setZoomScale:animated: method or zoomScale property, so please check your scroll views.
Make sure your zoomScale, minimumZoomScale and maximumZoomScale to set to at least 0.1.
Related:
Calculating minimumZoomScale of a UIScrollView
UIScrollView not respecting minimumZoomScale after changing the subview

How to show app-specific status bar at bottom of screen, outside of app?

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];
}
}

Resources