What is the closest ancestor to self.navigationController.navigationBar and a subview of self.view in a view controller so I can add the constraint to that ancestor?
self.searchBar = [[UISearchBar alloc] initWithFrame:CGRectZero];
[self.searchBar setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:self.searchBar];
NSLayoutConstraint* cn = [NSLayoutConstraint constraintWithItem:self.searchBar
attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
toItem:self.navigationController.navigationBar attribute:NSLayoutAttributeBottom
multiplier:1.0 constant:0];
I tried doing
[self.view addConstraint:cn];
[self.navigationController.view addConstraint:cn];
but both times I get the error, "Does the constraint reference something from outside the subtree of the view? That's illegal."
Can someone explain the view hierarchy of a view controller in relation to the navigationController?
Thanks!
The view tree looks like this:
So, navigationController.view is the closest ancestor of the navigation bar and your view. I'm not sure why that didn't work. Where did you put that code?
Related
I'm learning about View Controller Containers and nesting Viev Controllers in iOS, using swift and storyboard.
The problem is - added view is not within the bounds of a parent view.
This is how it looks in interface builder:
The left view is parent view controller, and the white area is view, in which I want to place blue view controller.
I imagine, that the blue view controller should fill this white view.
However, this is how it looks on iPad:
It looks, like the blue view is placed the same amount pixels below the top of white view, as is the white view to the top of the screen.
Here's the code:
class ViewController: UIViewController {
#IBOutlet weak var vcContainer: UIView!
override func viewDidLoad() {
super.viewDidLoad()
var vc = self.storyboard?.instantiateViewControllerWithIdentifier("TestViewController")
displayVC(vc!)
}
func displayVC(content: UIViewController) {
content.view.frame = self.vcContainer.frame
content.view.setNeedsLayout()
self.vcContainer.addSubview(content.view)
self.addChildViewController(content)
content.didMoveToParentViewController(self)
}
}
Any help would be appreciated. Thanks!
This line: self.vcContainer.addSubview(content.view)
it should be self.view.addSubview(content.view)
Because when you set frame for your child viewcontroller like this:
content.view.frame = self.vcContainer.frame
it is relative to parent viewcontroller, not vcContainer, hence after you added content into vcContainer, it's in wrong position.
If you want to add content into vcContainer, you should choose another way to place content.view, something like pin edges of content to edges of vcContainer using NSLayoutConstraint.
Full working code:
UIViewController *vc =
[self.storyboard instantiateViewControllerWithIdentifier:#"vc"];
[self addChildViewController:vc];
vc.view.translatesAutoresizingMaskIntoConstraints = NO;
[self.container addSubview:vc.view];
[self.container addConstraint:[NSLayoutConstraint
constraintWithItem:vc.view
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.container
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0]];
[self.container addConstraint:[NSLayoutConstraint
constraintWithItem:vc.view
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.container
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0]];
[self.container
addConstraint:[NSLayoutConstraint
constraintWithItem:vc.view
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.container
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0]];
[self.container
addConstraint:[NSLayoutConstraint
constraintWithItem:vc.view
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.container
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:0]];
[vc didMoveToParentViewController:self];
Adding constraints will be a lot easier if you use PureLayout.
The constraints set in your StoryBoard on your view specify an exact distance to be kept from the top.
This distance is true for some devices, so it might looks like it is in the middle, but on the ipad this is not the case.
You should add a height on your view, and than position vertically and horizontally to the center of your superview.
I have a view controller set up in a storyboard. The view controller's view contains a subview(UITableView) with pinned edges to the 4 sides of its parent, essentially making the view fill its parent.
I am adding the view controller's main view as a subview of another view controller's view like this:
UIView *overlayView = firstViewController.view;
[overlayView setTranslatesAutoresizingMaskIntoConstraints:NO];
UIView *sourceView = secondViewController.view;
[sourceView addSubview:overlayView];
NSLayoutConstraint *constraint;
constraint = [NSLayoutConstraint constraintWithItem:overlayView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:sourceView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0];
[sourceView addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:sourceView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:overlayView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:100];
[sourceView addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:overlayView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:sourceView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
[sourceView addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:overlayView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:sourceView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[sourceView addConstraint:constraint];
[sourceView layoutIfNeeded];
I want to have a gap from the right edge of the view to its parent equal to 100 pixels.
Now weirdly enough this works as expected on iOS 8, but fails to do so on iOS 7 and the view is displayed full-screen ignoring the 100 constant set on the trailing constraint. Also, if the view controller's view that is being added has no child views - then it is working as expected. Is setTranslatesAutoresizingMaskIntoConstraints: being applied to all the subviews of a view in iOS 7 ? What might be the reason for this ?
EDIT:
The issue lies somewhere in the way subviews are being managed by the OS.
When the Container View is set as a outlet to the view property of the view controller, things don't work. If however, I set the Table View as an outlet to the view property, the it works. Something weird happens if there is a child view with pinned edges to its superview and then I am adding other constraints to the superview. I dont understand why it works fine on iOS 8 though...
EDIT 2
The problem seems to happen only with the trailing constraint. If I want to modify the constants of any of the other constraints there are no issues ?!
Can you try interchanging sourceView with overlayView in your constraint?
`constraint = [NSLayoutConstraint constraintWithItem:sourceView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:overlayView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:100];`
This essentially means that sourceView.trailing - 100 = overlayView.trailing; which is not what you would want.
Edit 1:
Is setTranslatesAutoresizingMaskIntoConstraints: being applied to all the subviews of a view in iOS 7?
NO. translatesAutoresizingMaskIntoConstraints is set only for the views you explicitly call setTranslatesAutoresizingMaskIntoConstraints for.
I have implemented a popup UIView which I add to the topmost window via [[[[UIApplication sharedApplication] windows] lastObject] addSubview:popupView], so it will appear on top of everything even the keyboard. I need to ensure this popup view that I programmatically created will always remain centered on screen. I was attempting to add auto layout constraints, but it doesn't like the fact I'm trying to align with the topmost window. Could you let me know how I could accomplish this? Thank you.
This is what I have implemented, which will generate a (nicely detailed) error that states 'The view hierarchy is not prepared for the constraint ... the constraint's items must be descendants of that view':
[popupView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSLayoutConstraint *cn = nil;
cn = [NSLayoutConstraint constraintWithItem:popupView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:[[[UIApplication sharedApplication] windows] lastObject]
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0];
[popupView addConstraint:cn];
cn = [NSLayoutConstraint constraintWithItem:popupView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:[[[UIApplication sharedApplication] windows] lastObject]
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0];
[popupView addConstraint:cn];
cn = [NSLayoutConstraint constraintWithItem:popupView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:blockSize.height];
[popupView addConstraint:cn];
cn = [NSLayoutConstraint constraintWithItem:popupView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:blockSize.width];
[popupView addConstraint: cn];
Don't go sticking your view into a window that wasn't set up specifically to hold your views. You don't know what the real owner of that window might do, now or in a future version of iOS.
Create your own window and set its windowLevel high enough to be above the keyboard. Give the new window its own root view controller (so it will handle different orientations properly) and center your popup view inside that root view controller's view.
You will find lots of useful information in the answers to this question.
It's fine to add the height and width constraints to popupView, but since popupView is a subview of the window, the centering constraints should be added to the window, not the popupView (and you need to add the popupView as a subview first, before you add the constraints).
Can we add a NSLayoutConstraint between self.navigationcontroller.navigationbar and a view inside the self.view. Here self is a UIViewController instance and _textField is a subview of self.view
What I need is that the UI should look alike irrespective whether the navigationBar is Translucent or not.
I've tried the following. But It does not work.
NSLayoutConstraint* cn = [NSLayoutConstraint constraintWithItem:_textField
attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
toItem:self.navigationController.navigationBar attribute:NSLayoutAttributeBottom
multiplier:1.0 constant:20];
[self.navigationcontroller.view addConstraint:cn];
Yes you can add a constraint between the Navigation Bar and a view. Your root view conroller added to the navigation controller contains topLayoutGuide. so adjust your code like this:
NSLayoutConstraint* cn = [NSLayoutConstraint constraintWithItem:_textField
attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
toItem:self.rootViewController.topLayoutGuide attribute:NSLayoutAttributeBottom
multiplier:1.0 constant:20];
[self.rootViewController.view addConstraint:cn];
notice that i'm not referencing the navigation controller at all but the rootViewController of the navigation Controller.
Also you can use bottomLayoutGuide to go above the TabBar the same way. (however if you need to do that you'll run into a bug in iOS frameworks with a workaround patch here: UIViews ending up beneath tab bar )
Check out the topLayoutGuide property on UIViewController.
There's an example in Apple's doc for `UIViewController' that goes like this...
topLayoutGuide
Indicates the highest vertical extent for your onscreen content, for use with Auto Layout constraints. (read-only)
#property(nonatomic, readonly, retain) id<UILayoutSupport> topLayoutGuide
And then...
As an example of how to programmatically use this property with Auto
Layout, say you want to position a control such that its top edge is
20 points below the top layout guide. This scenario applies to any of
the scenarios listed above. Use code similar to the following:
[button setTranslatesAutoresizingMaskIntoConstraints: NO];
id topGuide = myViewController.topLayoutGuide;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings (button, topGuide);
[myViewController.view addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat: #"V: [topGuide]-20-[button]"
options: 0
metrics: nil
views: viewsDictionary]
self.view layoutSubviews; // You must call this method here or the system raises an exception
];
Add the constraint between the top of the textField and the top of the parent view. The constant for the constraint can be set to the height of the status bar + height of the navigation bar.
Obviously, the following code snippet will only work if both the Status Bar and Navigation Bar are translucent and the view controller wants full screen layout. You can easily test for transparency and adjust accordingly, if necessary.
If you're using interface builder, you can also create an IBOutlet for the existing constraint and just set it's constant rather than creating a new constraint.
// Obtain the view rect of the status bar frame in either portrait or landscape
CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
CGRect statusBarWindowRect = [self.view.window convertRect:statusBarFrame fromWindow: nil];
CGRect statusBarViewRect = [self.view convertRect:statusBarWindowRect fromView: nil];
// Add Status Bar and Navigation Bar heights together
CGFloat height = self.navigationController.navigationBar.frame.size.height +
statusBarViewRect.size.height;
// Create & Add Constraint
NSLayoutConstraint *constraint =
[NSLayoutConstraint constraintWithItem:self.fieldLabel
attribute:NSLayoutAttributeTop
relatedBy:0
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1
constant:height];
[self.view addConstraint:constraint];
I have created a UIView using the following code within viewDidLoad (where 'secondview' obviously is the name of the UIView):
secondview = [[UIView alloc] initWithFrame:self.view.frame];
[secondview setBackgroundColor: [UIColor yellowColor]];
secondview.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:secondview];
Then within viewDidAppear I added constraints to this view:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:secondview attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0f constant:-20.0f];
[self.view addConstraint:constraint];
However, the constraints are not applied to the view (atleast not that I can see). Instead, the view simply seems to disappear from the screen. If the constraint code is commented out however, the view once again loads with the appropriate frame (obviously without the constraints being applied). When applying the same constraints to a Button or ImageView, the constraints are applied perfectly. This has lead me to think that the issue is because of 'initWithFrame' when creating the View, as neither the button nor ImageView actually require it's size to be specified.
What are your thoughts? What should I do differently?
For anyone who comes across this... I needed to add more than one constraint. That did the trick.