Organizing NSLayoutContraints between code and IB - iphone

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.

Related

iOS7 - View under status bar - edgesForExtendedLayout not working

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

How to put AdWhirl-banner fixed above TabBar?

My app consists of a TabBar and several TableViews. I want to have the AdWhirl-banner fixed just above the TabBar (only in the first TableView), but thus far I have not been succeeding.
Until now, I have implemented the following code into my TableViewController:
AdWhirlView *adWhirlView = [AdWhirlView requestAdWhirlViewWithDelegate:self];
[self.tableView addSubview:adWhirlView];
adWhirlView.center = CGPointMake(160, 342);
And it indeed shows the Ad i want to see, only it is partly covered by a section header (from the TableView), and when scrolling the Ad scrolls along.
How can I achieve that the Ad is both on top (in terms of top view) and at a fixed spot (above the TabBar)?
Any help is greatly appreciated!
You need to add it to the tabBar layer.
Try this:
[self.tabBarController.view addSubview:adWhirlView];
You may need to reposition it do it's not underneath the tabbar.
I also add a footer to the tables so they can be scrolled all the way up without the ad getting in the way.
You will likely need to create your own subclass of UIViewController, adding instances of both the AdWhirlView and UITableView to its view, either programatically, or in the nib file.
Anthoer, hackier way to do this would be to add the AdWhirlView as a header view in the existing table, but I'd opt for the first way. It will give you more control over how you want it to look and behave.
I'd think another alternative here would be to have a container UIView that contains both a UITTabBar which contains a UITableView. If you're on the right tab, you could just request an ad. If the request is successful, just shrink the tabbed view enough to make room for the ad at the top and insert it into the container UIView.

Add UIViewController over another but buttons on parent still clickable

.m file.
- (IBAction)switchViewThinking:(id)sendr {
[self.view addSubview:pick.view];
pick.view.alpha=1.0;
[pick animate];
}
The view pops up fine. Only the buttons on the parent view are still clickable behind the subview that overlays it. Also pick is a UIViewController. The parent view has 3 buttons each open a differnt subview. On the subviews buttons are not clickable through UITables or Scrolltext. Linkage is correct and views remove and activate when needed to. Is there a setting I'm missing that would cause this? Whats the best solution to overcome this.
Thanks
You'll have to either re-create the buttons on the top layer (maybe as transparent clickable views) or make the top layer smaller so that it doesn't obstruct the buttons below it. Instead of one large view on top, you could create many smaller views that look like one view,but that still allow the buttons beneath to be visible and clickable.
In any case, I'd advise thinking whether you really want this behavior. Seems a little like it would not be intuitive for the end user.

iPhone UIImage overlapping text

I have a view with a UIScrollView, UIImageView for a background, and a UITextView. I have created several other views just like this and they all come out okay - with a background image and scrollable text but for some reason, now I can't make that work. Either my image overlaps all of the text so that I can't read it or the UITextView default background (white) shows up so that the user can't see the background image. What could be causing this problem?
Do you use Interface Builder or build the views hierarchy in code?
In both cases you should make sure that the order of your views is correct.
In IB the view that you want to appear on top of all the rest has to be under the rest of the views.
In code, make sure that the text view is the last to be added to the hierarchy.
You could also use the next code in order to check if this is the problem:
[self.view bringSubviewToFront:textView];
Okay, it must have had something to do with choosing the delegate. I can't say that I completely understand how I fixed it but it had to do with declaring the delegate in IB.

How to add UIScrollView to Interface builder?

I have all my controls laid out in interface builder (many labels, buttons etc). How do I put them all in a scroll view in interface builder so that I can have more space and be able to scroll up and down to reveal more controls? Do I have to do this programatically?
Open the view that has all the controls and labels, etc. (in Interface Builder). Select All. Then under the Editor menu, select Embed In, then Scroll View.
Note: in older Xcode versions, this is under the Layout menu, then Embed Objects In... (scroll view).
My preferred solution, where you don't need to hard-code the size of the contentSize:
NB: you might be able to avoid the source-code parts of this using the trick here: https://stackoverflow.com/a/11239123/153422 - although I haven't tried it yet.
The rest of this trick ... you still need to use anyway
Move all controls into a single UIView (in IB: select all, then go Layout > Embed Objects In ... > View)
Hookup that single UIView to your source code using an IBOutlet property (see below)
IN SOURCE CODE, NOT INTERFACE BUILDER (IB is broken here, it has bugs where it sets the origin of the UIScrollView incorrectly - it tries to center the view. Apple never bothered to check it for basic bugs, sigh): Move the single UIView into a UIScrollView (see code below).
Use sizeThatFits to "automatically" set the correct size.
Code (StackOverflow won't let me put code inside a numbered list. Sigh)
Header file:
/** outlet that you hook up to the view created in step 1 */
#property(nonatomic, retain) IBOutlet UIView *masterView;
Class file:
/** inside your viewDidLoad method */
[scrollview addSubview: masterView]; // step 3
scrollView.contentSize = [masterView sizeThatFits:CGSizeZero]; // step 4
...although I haven't checked this recently, IIRC it works on both 2.x and 3.x
Select all the objects you want to put into a scroll view and go to the Layout menu, choose "Embed Objects In" and choose "Scroll View".
Its easy:
First add a scrollview to your view.
Change the size of the scrollview (e.g. make it 700 pixels long).
Start putting your controls
When you want to put/edit controls in the lower (invisble) part, select the scrollview and change the Y-start position to -300.
Voila.
After editing set the Y-start position back to 0 or whatever it was.
I don't know if it's just me, but I tried to follow the instructions in each of the other answers here and none of them worked. None of the answers included everything needed, each one I guess assuming we know to do something so leaving that part out. I finally figured it out with the help of red artisan. So... I am listing here ALL the necessary steps to get this to work:
In InterfaceBuilder, add a View and then add your controls to it (or if your controls already exist in the main view, you can select all your controls and then go to Editor | Embed In | View, then drag that new View so it is all by itself outside the main view). This View can be any size you like.
In InterfaceBuilder, add a Scroll View to your main view, and size it to take up the whole main view.
Add the code listed below to your UIViewController Header and Class files.
In InterfaceBuilder, hook up the View containing your controls to 'contentView' in the File's Owner. Hook up the Scroll View to 'scrollView' in the File's Owner.
Header File:
#interface MyViewController : UIViewController
#property(nonatomic, retain) IBOutlet UIScrollView *scrollView;
#property(nonatomic, retain) IBOutlet UIView *contentView;
Class File:
#synthesize scrollView, contentView;
- (void)viewDidLoad
{
[super viewDidLoad];
[self.scrollView addSubview:self.contentView];
self.scrollView.contentSize = self.contentView.bounds.size;
}
- (void)viewDidUnload
{
self.scrollView = nil;
self.contentView = nil;
[super viewDidUnload];
}
Although this question is very old, I will suggest a workaround I found as I had the same issue and wasn't able to find much help out there:
When in IB, if you want to place objects outside the 420 pixel, just make sure yourself of having selected Unspecified for all of Status Bar, Top Bar, and Bottom Bar for the View that contains the Scroll View with all the objects. This way, you'll be able to manually resize the screen (for the View). Then, you can follow Ximonn's advice on resizing the H value for the Scroll View, having access to all the other objects, working with them and then, undoing changes for H value and then setting the needed Bars.
Important little thing.
To scroll big subview (UIImageView for example) in UIScrollView remember, for this subview, uncheck "User Interaction Enabled" checkbox in InterfaceBuilder -> View window. Or do it programatically.
subview.userInteractionEnabled = NO;
Otherwise this subview will stack on screen without any effect.
I've been looking for this for a few days, and I finally came across this site with a solution that worked for me.
Scrolling with UIScrollView
Basically you have your main view with a UIScrollView object in it. Then another content view with all your content in it. Then you add the content view to the scroll view. And then finally set the size of the scrollview's content size to the size of the content view.
I know, this thread is a bit older... But somebody could find it on google, it's hight ranked.
I wrote this little helper Method to get the job done:
- (void)addSubview:(UIView *)theSubView toScrollView:(UIScrollView *)theScrollView
{
[theScrollView addSubview:theSubView];
theScrollView.contentSize = theSubView.bounds.size;
}
You just have to declare two IBOutlet's in the header (e.g. contentView and scrollView) and call the method like this, whereever you want to load a UIView into a UIScrollView with your sourcecode:
[self addSubview:contentView toScrollView:scrollView];
I called it in viewDidLoad
This method features iOS
The selected answer works well for Xcode 3.
However, for Xcode 4, menus have been re-arranged slightly.
To do the same in Xcode 4 select your views then use:
Editor > Embed In > Scroll View