iOS7 - View under status bar - edgesForExtendedLayout not working - iphone

I have a project that was built last year, and it uses XIBs, no storyboards. The XIBs do not use Auto Layout, but they do use some Autosizing. I have an issue when running with iOS7, in which all the views are tucked under the status bar. I fully understand this is a new feature with iOS7, in which this can be expected. However, all of the solutions for fixing it to not do this are not working. I have an image at the top of the view that always shows under the status-bar, and I'm not using nav-bars or anything like that.
I have tried updating the Y-deltas in the XIB (they have no effect on the view), I have tried setting the edgesForExtendedLayout to UIRectEdgeNone (does nothing), and a multitude of other things. Every time, the status bar shows with the view tucked under it, no matter what I do.. that is unless I manually move down the view in the XIB to allow room for the status bar (but that solution doesn't work because it doesn't look right in iOS6, of course).
What's odd is that even when I try a line of code to hack in a view-shift, it doesn't work (like the following):
self.view.frame = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y+20, self.view.frame.size.width, self.view.frame.size.height);
..Not that I would go with that kind of solution, but it's just odd that it didn't work (the only time I typically see that not work is if Auto Layout is in place, which it's not in this case).
It is a design requirement that the status-bar shows, and I'm just stumped on why I can't set the view to be under the status bar for iOS7. I have read every single Stack Overflow post on the subject, as well as Apple's transition/guides. Once again, to reiterate, I fully understand how it should function and what the expected solution should be to this, but none of that seems to be working for this particular project.
I am an experienced iOS dev, but this project was built by another team, so I don't know if there's something hidden somewhere in the XIB files, plist, or code that could be trumping the above settings. Please let me know if there is something else that can be looked at on this, or more information I can provide.
Thanks in advance!

If you set the iOS 6/7 delta values in Interface Builder, remember to set "View as" to "iOS 6" on the Interface Builder Document, since it is the iOS 6 layout you want to replicate. The deltas will then be used only on iOS 7 to push the content below the status bar. If you leave "View as" set to iOS 7 (the default) the deltas will instead give you the iOS 7 look on iOS 6.
However, the deltas will not help you if you reposition or resize views programmatically based on the view frame, since the frame does not account for the deltas.
Instead of using the deltas, the best solution I have found is to enable Auto Layout on your main XIB and then set the top space constraint on your top/content view to follow the Top Layout Guide. This guide was introduced in iOS 7 and represents the position below the status bar. Unfortunately the guide is not available in Interface Builder when not using Storyboards, but you can add it programmatically.
What I did was add a top space constraint to the superview instead in Interface Builder, and created an outlet for this in the code. Then, in viewDidLoad, if the topLayoutGuide is available (iOS 7+), replace the constraint in this outlet with a version using the Top Layout Guide instead.
if ([self respondsToSelector:#selector(topLayoutGuide)]) {
[self.view removeConstraint:self.containerTopSpaceConstraint];
self.containerTopSpaceConstraint =
[NSLayoutConstraint constraintWithItem:self.contentView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.topLayoutGuide
attribute:NSLayoutAttributeBottom
multiplier:1
constant:0];
[self.view addConstraint:self.containerTopSpaceConstraint];
[self.view setNeedsUpdateConstraints];
[self.view layoutIfNeeded];
}

For reference, the solution below did work when I applied it to my ViewControllers. However, it's not ideal and a bit hacky. If it's the only approach I can take, then so be it, though.
float systemVersion=[[[UIDevice currentDevice] systemVersion] floatValue];
if(systemVersion>=7.0f)
{
CGRect tempRect;
for(UIView *sub in [[self view] subviews])
{
tempRect = [sub frame];
tempRect.origin.y += 20.0f; //Height of status bar
[sub setFrame:tempRect];
}
}

Apple are pushing you to use autolayout to accomplish this. You need to set a constraint to the "Top Layout Guide" from the top subview in your view.
See this document for examples:
https://developer.apple.com/library/ios/qa/qa1797/_index.html
To do this without XIBs, you'll need to add the constraint programatically. Apple's docs give a good example of this, which I've summarised below.
Giving that the topLayoutGuide is a property on a view controller, you just use it in your dictionary of variable bindings. Then you set up your constraint like normal:
id topGuide = [myViewController topLayoutGuide];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(button, topGuide);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[topGuide]-20-[button]" options:0 metrics:nil views:viewsDictionary];
The documentation for this can be found in the UIViewController class reference.

1)The simplest solution if you don't mind having an opaque navigation bar:
self.navigationController.navigationBar.translucent = NO;
2) svguerin3's answer can't work in the general case. For example, if one of your subviews uses autosizing to be hooked at the bottom of its container, then its new position will be wrong. And it could go out of screen in the worst case.

- (void)viewDidAppear:(BOOL)animated {
[self.view setFrame:CGRectMake(0, 20, self.view.frame.size.width, self.view.frame.size.height)];
// OR
self.view.transform = CGAffineTransformMakeTranslation(0, 20);
}

Have you tried viewing your XIBs as source and removing any line containing edgesforextendedlayout ??
We had to remove this line in our storyboard's scenes since our storyboard's scenes' main views are represented by XIBs
What was happening for us was that somehow, in some scenes, the XIB content for the scene's main view was being pushed down by the height of the status bar and the navigation bar.
Removing that line allowed the XIBs to be displayed as if their top originated at the same top of its storyboard's scene.
Sadly, we have no idea what triggered this, but I saw it happen when changing the order of the contents within the XIB's main view so that a UITextView appeared first. Rearranging the order of items after this was triggered had no effect in removing this unwanted behaviour.
Hope this helps anyone else running into this type of problem.

If you are using the storyboard, after setting the top layout of your view, you can uncheck the "Under Opaque Bars" in the Attributes Inspector

Related

Organizing NSLayoutContraints between code and IB

I'm sure that this must be a common issue. I have a UIViewController which has a UINavigation bar, and a ContentView (which contains UIView and a bunch of children controls. The user can hide this content view by swiping left, then it animates off the left side of the screen. I want the navigation bar to expand and fill the entire width. You cannot link these two with Interface Builder, so it must be done in code. No problem:
// Called from viewWillAppear
-(void)addLayoutConstraints{
UINavigationBar *navigationBar = self.navigationBar;
UIView *facetView = self.facetViewController.view;
NSDictionary *views = NSDictionaryOfVariableBindings(navigationBar, facetView);
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-[facetView]-[navigationBar]-|"
options:NSLayoutFormatAlignAllBaseline
metrics:nil
views:views];
[self.view addConstraints:constraints];
}
That code snipped should do it just fine. The problem is that I have laid out the UINavigationBar in IB, so that I can easily add customized buttons to it and see it, etc... The issue is that when I layed out the navigation bar in IB, IB adds a bunch of default constraints that cannot be removed. One of which is in conflict with the one I added in the code above.
How do you get around this? Here are 3 possibilities I came up with:
1.) I could create the UINavigation bar programmatically as well. This way it only has the constraints that I add to it. I'd prefer not do it this way, but it's an option
2.) Using IB, somehow delete the default constraints so that they don't' collide. I do not see a way to do this
3.) In code, remove the default constraints that IB adds to the UINavigation bar, then add my custom constraints. I do see that there is removeConstraint and removeConstraints. I suppose I could retrieve the constraints, remove them, then add my own. Again, kinda long messy process.
What is your opinion? Have you done similar?
Option 3 is the supposed way. It is exactly talked about in this WWDC 2012 session — Introduction to Auto Layout for iOS and OS X.

Using AutoLayout in Xcode disables my code from working (dynamically)?

In my project I have enabled AutoLayout in order to make the app scale properly in both iPhone 4 and iPhone 5. Everything worked fine with doing so, but I found a new problem which I am not sure how to handle.
In my project I have a normal method which checks if a boolean is yes or no, if yes the interface should add a button into the interface and keep my tableView in its current state. But if the method returns no, the button should disappear and make the tableView's height higher! Everything with the button works fine but for some reason after start using AutoLayout my code for increasing the tableView's height stopped working (which worked before).
Now what can I do to make my tableView's height increase in height even if I am using AutoLayout? Here is my code:
- (void)viewWillAppear:(BOOL)animated
{
NSString *bookName = [self getCurrentBookName];
if([self isBlank:bookName])
{
[self.currentBookLabel setText:NSLocalizedString(#"LabelNoBookChosen", nil)];
}
else
{
[self.currentBookLabel setText:[self getCurrentBookName]];
}
[super viewWillAppear:animated];
if([self isAppLicensed] != YES) <------------------THIS IS WHERE I CHECK THE BOOLEAN METHOD!
{
[actionAppStore setHidden:TRUE];
CGRect framez = [tableView frame]; <------------------ THIS CODE IS NOT WORKING ANY LONGER!
[tableView setFrame:CGRectMake(framez.origin.x, framez.origin.y, framez.size.width, framez.size.height+77)];
}
else
{
[actionAppStore setHidden:FALSE];
}
}
You can either
Turn off autolayout and use the autosizing masks. The non-autolayout autosizing masks give you the same control over having controls increase or decrease their size for the 3.5" screen vs the 4" screen. This is probably the easiest solution. And it gives you compatibility with iOS 5 devices, too.
If you want to use autolayout, then you should create an IBOutlet for the appropriate constraint, and then programmatically change that.
On that latter example, consider a layout where I have a table view and a control at the bottom for the app store (I'm inferring this from your variable names). There are two ways to hide and show that bottom control. One is to hide it (or set its alpha to zero or to removeFromSuperview), remove the unnecessary constraints, and recreate the new appropriate constraints.
That works, but it's a hassle. I now prefer to either change the height of what I want hidden to zero, or, if it's already on the edge of the screen, I'll just slide it off the edge so you can't see it anymore.
Thus I might visually hide the app store control, not by playing around with it's hidden property, but rather by changing its height to zero (or, to show it, to 77). That way, the other constraints will automatically resize the other controls. Thus I hide it with:
self.appStoreHeightConstraint.constant = 0.0;
[self.view layoutIfNeeded];
And I show with:
self.appStoreHeightConstraint.constant = 77.0;
[self.view layoutIfNeeded];
I do that with an IBOutlet called appStoreHeightConstraint which is linked to the height constraint for that bottom control.
Alternatively (and only if the item being hidden is at the bottom), I can slide the it off the bottom of the screen (this time with a IBOutlet on the bottom constraint), with:
self.appStoreBottomConstraint.constant = 77.0;
[self.view layoutIfNeeded];
and show it with
self.appStoreBottomConstraint.constant = 0.0;
[self.view layoutIfNeeded];
In the interest of full disclosure, I should mention that you have to be very careful about designing the constraints so that they simultaneously (a) minimally describe your layout; but (b) fully describe your layout. You want to avoid the horrid conflicting or unsatisfactory constraint messages.
Focusing on the vertical dimension only, that means that have the following constraints:
table view's top constraint was to the top of the superview;
table view's bottom constraint was to the top of the app store view;
table view should have no height constraint (because that's what you want to change as other stuff changes);
the app store control at the bottom had it's top constraint linked to the tableview;
the app store's bottom constraint linked to the bottom of the superview; and
the app store's height constraint fixed at 77.0.
But I find that IB always (in a good faith effort to ensure that the constraints are unambiguous) is trying to add additional constraints (e.g. the height of the tableview). So, I personally wrestle with IB to get the constraints quite right (usually I end up lowering the priority of the table view height and when I'm all done with everything else, I can go back and get rid of the table view height).
Maybe I'm making too much of the hassles in editing the constraints, but I mention it because if you don't get them all exactly right, efforts to effect UI changes by altering one constraint will not yield the results you want. Once you get the hang of it, it's pretty easy and it's a thing of beauty to see everything adjust accordingly (esp on complicated scenes). But it can be a hassle to do those first few times.
You probably need to tell the view to layout its subviews using layoutIfNeeded.
See my question I just solved and this question is also related.

iPhone Multi-View Rotation Hell

This is my first post on here and I'm very new to iPhone developing, so please bear with me.
I have an app which has a few view controllers and each view controller has a few nib files that it controls and some of the nibs have more than one view. There is a toolbar throughout the app, controlled by the root view controller.
After lot's of searching, I have got rotation working on every screen, except one. I haven't got them to load in the correct orientation, but I guess that's a different question.
In my nibs that have more than one view, I can select the autosizing options only on the original main view. On the views that I have added, which I load using insertSubview, I can select the fixed margin options, but not the width and height resizing options. I am using Xcode 4.
My 1st Question, is why I am unable to select the resizing options on the additional views?
Sorry, I had to delete the images I put in here, showing what I mean, to be able to post.
Anyway, that is not my main problem, I just want to know, out of curiosity, why I can't select the resize options. I have found a way around it in the code. In the function where I load the subview, I add [viewName setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
E.g to load the view that I have named shiftStart, I use:
- (IBAction)loadShiftStart:(id)sender {
[self clearView];
[self.view insertSubview:shiftStart atIndex:0];
[shiftStart setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
}
Now here is my problem. I have a nib file that has 3 views, view which is the main view that loads first, rotaStart, which loads as a subview when a button on the first view is pressed and shiftStart, which loads as a subview when a button on the first view is pressed. rotaStart and shiftStart are almost identical, with a title, a text block, a Yes button and a No button. The only difference is the text in the box, and which view is loaded if you click on the Yes or No buttons.
I have all the same resize and margin options selected, in Xcode, for the title, text block and buttons and as far as I can tell, the code that loads them is identical (see code below), but shiftStart rotates correctly and rotaStart does not.
- (IBAction)loadRotaStart:(id)sender {
[self clearView];
[self.view insertSubview:rotaStart atIndex:0];
[rotaStart setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
}
- (IBAction)loadShiftStart:(id)sender {
[self clearView];
[self.view insertSubview:shiftStart atIndex:0];
[shiftStart setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
}
Can anyone tell me why rotaStart is not rotating, when the phone is changed to landscape please?
Sorry this explanation has got a bit long winded. I'm not sure how else to explain it.
Thanks
For anyone else looking at this, with a similar problem, #longball solved it for me. Somehow I had managed to add a second view over the top of the base view, complete with Labels & Buttons. Deleting the extra view fixed the problem.

Can't resize UIView in IB

Probably something simple, but I can't figure why I cannot resize a UIView in a xib in Interface Builder.
I created a new view XIB in xcode and in the size inspector, the width and height are disabled and grayed out to 320 by 460. This is strange since I can change the size for the other two views (associated with the other two tab bar items).
I am not sure if this has anything to do with but I recently updated the sdk to 3.
Thanks!
In order to achieve that in XCode 4.6.3/5.0.2 you need to follow the screenshot below:
Under 'File Inspector' -> untick Use Auto Layout
Click on Attributes Selector & Choose Freeform for Size
This is needed when creating a headerView for a tableView
I think that you cannot edit the size while simulating any user interface elements such as the status bar in Interface Builder. You could try turning the simulated elements off if any are enabled. Not exactly sure why this is, so would appreciate feedback from anyone who knows why exactly this is - there must be a good reason.
The part that you are looking for is on the first Inspector panel (Command-1). Right at the top there is a section that is labeled Simulated User Interface Elements. By default the Status Bar is on. You need to make sure all three are set to None.
As at Xcode 4.2, iOS 5.0.1, the default simulated metrics include simulating the 'size' attribute of UIViewControllers to 'Inferred'
If you change this attribute to 'Freeform' then you are able to resize the UIView contained within the UIViewController while also simulating the status bar (or any other of the simulations for that matter)
Just go to the inspector, go to the first panel (or press command-1) and then change the fields marked Status Bar, Top Bar, and Bottom Bar to None under the section called "Simulated User Interface Elements". You may need to click the little triangle to make them visible.
Funny I have a similar problem. I've removed all views from the xib file and re-added a view. Set the status bar to being "unspecified" so that the status bar is not accommodated for. The view, when added, get created with x=0, y=0, w=320 h=460 instead of (0,0,320,480) as I would have expected. After changing the height to 480 and connecting it to file's owner and changing the color to make it distinctive in relation to the background. I fount that there was a white band between the top of the screen and the view.
I augmented my code with:
#define RECTLOG(rect) (NSLog(#"" #rect #" x:%f y:%f w:%f h:%f", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height ));
within viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
RECTLOG(self.view.frame);
}
Turns out that the view is as it's loaded from IB is actually (0, 20, 320, 480)
As a work around I reset the view's frame with self.view.frame = CGRectMake(0, 0, 320, 480);
I'm using IB 3.2.5
I'd love to know of a little fix for this one.
I've noticed that the UIView automatically created when creating the xib file cannot be resized from the sizing panel or the edge handles.
All I did was to add another UIView and dragged everything from my old UIView to the new one then deleted the old one.
It solved the problem for me but yes, I think there is an underlying issue with the interface builder for Apple to solve (somebody correct me if I'm wrong).
I'm Using XCode 4 integrated Interface builder.
I sorted it out in a different way...
open your xib with your favourite text editor and edit this line
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="CompassViewController">
CompassViewController is your class name you are trying to link your nib/xib with.
Just edit your class name with the correct class name and you are done ;)
In storyboard select the UIVIew and go to the attributes inspector change the UIVIew size to "FreeForm" instead of "Inferred"
In Xcode 13
Storyboard -> Attribute inspector -> Size -> Freeform

Using a UITableViewController with a small-sized table?

When using a UITableViewController, the initWithStyle: method automatically creates the underlying UITableView with - according to the documentation - "the correct dimensions".
My problem is that these "correct dimensions" seem 320x460 (the iPhone's screen size), but I'm pushing this TableView/Controller pair into a UINavigationController which is itself contained in a UIView, which itself is about half the height of the screen.
No frame or bounds wrangling I can come up with seems to correctly reset the table's size, and as such it's "too long", meaning there are a collection of rows that are pushed off the bottom of the screen and are not visible nor reachable by scrolling.
So my question comes down to: what is the proper way to tell a UITableViewController to resize its component UITableView to a specified rectangle?
Thanks!
Update I've tried all the techniques suggested here to no avail, but I did find one interesting thing: if I eschew the UINavigationController altogether (which I'm not yet willing to do for production, but as an experiment), and add the table view as a direct subview of the enclosing view I mentioned, the frame size given is respected. The very moment I re-introduce the UINavigationController into the mix, no matter if it is added as a subview before or after the table view, and no matter if alloc/init it before or after the table view is added as a subview, the result is the same as it was before.
I'm beginning to suspect UINavigationController isn't much of a team player...
Update 2 The suggestion to check frame size after the table view on screen was a good one: turns out that the navigation controller is in fact resizing it some time in between load and display. My solution, hacky at best, has been to cache the frame given on load and to reset it if changed at the beginning of tableView:cellForRowAtIndexPath:. Why there you ask? Because it's the one place I found that worked, that's why!
I don't consider this a solution as it's obviously improper, but for the benefit of anyone else reading, it does seem to work.
Why not just use a regular UIViewController and create the table manually?
I had the same problem and I solved it with:
-(void) loadView {
[self setView:[[[UIView alloc] initWithFrame:CGRectZero] autorelease]];
[[self view] setAutoresizesSubviews:NO];
/* Create & configure table and other views... */
[self setResultsTable:[[RadarTableViewController alloc] initWithNibName:nil bundle:nil]];
[[resultsTable view] setFrame:CGRectMake(0,45,320,200)];
}
This is done in the parent (just a plain UIViewController in my case) controller.
I had the same problem and I solved it by resizing the tableView in the viewDidAppear function of the UITableViewController. Not the ideal solution but it works.
You can set the top margin by using :
UIEdgeInsets inset = UIEdgeInsetsMake(50, 0, 0, 0);
self.tableView.contentInset = inset;
it's Not a good practice , just in case you want more space on top you can use it .
Should not use UITableViewController , Just simply Use UIViewController and Programmatically Create UITableview
I agree with Ben's answer. I've often run into the situation where I need to resize a UITableVIew due to other controls on a view.
I usually just have a regular UIViewController with a UITableView IBOutlet. Then, if I need to, I can just manipulate the UITableView object's frame to get it to the size I need.
I'm not sure why you're creating an additional view controller for your table. However, in your code, I don't see you adding the table view to its parent. You might also try reducing the bounds height until the whole thing appears on screen; once you do that, it may give you insight as to why it's not working the way you expect.
Check the autoresizingMask and contentMode properties of the UITableView. These can both affect the frame.
i fixed this problem by this code
{
UIEdgeInsets insets;
insets.left = 0;
insets.right = 0;
insets.top = 0;
insets.bottom = 60;
self.tableView.contentInset = insets;
[self.tableView setScrollIndicatorInsets:insets];
}
If you are using Interface Builder, you can simply go to "Table View Size" properties window, and change Bottom Insets for both Content and Scroller with the height of another widget.
Try:
self.edgesForExtendedLayout = UIRectEdgeNone;
//This one in case your NavigationController is not Translucent
self.extendedLayoutIncludesOpaqueBars = NO;
Hope this helps.
Set the frame in UINavigationController.