Adding Autolayout programatically to existing autolayout Views added in xibs - objective-c

I have a view like below which has auto layout set in xibs.
Now I need to add a error notification, which will animate from top and the existing views will shift down, like this image below.
I know I can do this easily by simply adding this error view in my existing view controller and managing its height constraints. But I have a couple of other views where I need to re-use this error view. So, now I have created a custom view for this error view. Now my main problem is adding this to my mainview programatically with autolayout. So I need to add the blue error view in my self.view and remove the top layout constraint of the green view and set its top to the blue error view. Make sense? Below is my code for error view and adding it to self.view. But even that doesn't work, am I doing anything wrong here. Any help is appreciated.
-(id)initWithdelegate:(id)parentSelf ForView:(UIView *)parentView
{
if (self = [super initWithFrame:CGRectMake(0, 0, 100, 100)])
{
// Initialization code
self=(ErrorView*)[[[NSBundle mainBundle] loadNibNamed:#"ErrorView" owner:nil options:nil]objectAtIndex:0];
self.delegate=parentSelf;
[parentView addSubview:self];
self.hidden = YES;
[self setConstraintsToParentView:parentView];
}
return self;
}
-(void)setConstraintsToParentView:(UIView *)parentView
{
[parentView setTranslatesAutoresizingMaskIntoConstraints:NO];
//Setting width equal to parentview
[parentView addConstraint:[NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:parentView
attribute:NSLayoutAttributeWidth
multiplier:1
constant:0]];
//Setting fixed height of 50
[parentView addConstraint:[NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:50]];
//Setting x pos to center
[parentView addConstraint:[NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:parentView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
//Setting top position to self.view with constant 20
[parentView addConstraint:[NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:parentView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:20]];
}
I'm calling the errorView like this from my viewcontroller
myErrorView = [[ErrorView alloc]initWithdelegate:self ForView:self.view];

you can use identifier propertiy of constraints
NSArray *ArrayConstraint = yorlableorView.constraints;
for (NSLayoutConstraint *ob in ArrayConstraint)
{
if ([ob.identifier isEqualToString:#"yourIdentifier"])
{
ob.constant = 0 ;
// set your constant you want
}
}

Could you just add the error view as the top view with a height of 0 and then change the height constraint constant when you want to display it? You can animate this and it will simulate it pushing everything down, however with this approach it won't look like it is coming down from the top of the screen.
If you're happy with this approach you could just add a container view to your view with the buttons and tableview, then after you initialise the error view from the xib just assign it to the container, this will save you having to pass the parent view, adding it as a subview and adding the constraints programmatically in your error view class.
Let me know if need more detail or a concrete example. Hope this helps, good luck. Also just as an aside, you could look into Masonry, it makes adding constraints programmatically much easier.

Related

What can prevent Auto Layout constraints from being respected during animations?

I'm trying to snap a view to a UITabBarController's tabBar (UITabBar) to never hide it (the way Apple does it in the 'Featured' tab of the tvOS App Store).
I can make it work by setting up my constraints in a UIViewController that's directly contained in a UITabBarController. When the tab bar is hidden and shown, the view (in my case, a UICollectionView) follows perfectly with the animation. But it doesn't work as well when my UIViewController is in a UINavigationController. It eventually updates, but while the UITabBar is animating (hiding and showing), it doesn't update.
Here's how I set my constraints using NSLayoutConstraints:
UIView *targetView = self.collectionView;
NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:targetView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.tabBarController.tabBar attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:targetView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:0];
NSLayoutConstraint *constraint3 = [NSLayoutConstraint constraintWithItem:targetView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:1 constant:0];
NSLayoutConstraint *constraint4 = [NSLayoutConstraint constraintWithItem:targetView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
[self.view.window addConstraint:constraint1];
[self.view addConstraints:#[constraint2, constraint3, constraint4]];
Here's how I set those same constraints with Masonry (both have the same result, but this is much more readable):
[self.containerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.width.height.equalTo(self.view);
make.top.equalTo(self.tabBarController.tabBar.mas_bottom);
}];
Here's a sample project demonstrating my issue:
https://www.dropbox.com/s/bxbi0gyidxhu2bz/SampleMovingWithTabBar.zip?dl=1
Is this a bug or is it expected behavior ?
I'm trying to implement this "view not hiding under the tab bar" behavior in another more complex app and I get the same odd behavior. Though in this case there are no UINavigationController involved. The view is directly in a UIViewController (which is part of the viewControllers array of the UITabBarController).
What can prevent Auto Layout constraints from being respected during animations ?
This how what my View Controller hierarchy looks like in the demo project I linked to (the View Controller that is within the UINavigationController is the one that does not animate when showing/hiding the tab bar):
<UITabBarController 0x7fca42d10bb0>, state: appeared, view: <UILayoutContainerView 0x7fca42f848b0>
| <FirstViewController 0x7fca42d11340>, state: disappeared, view: <UIView 0x7fca42f96510>
| <UINavigationController 0x7fca43812000>, state: appeared, view: <UILayoutContainerView 0x7fca42d46200>
| | <FirstViewController 0x7fca42f347f0>, state: appeared, view: <UIView 0x7fca42f8bd90>
Whenever you use CoreAnimation API's for animations at that time layout constraints do not respect constraints during transient Animations.
When you use NSLayoutconstraint's property like constant for animation then auto layout does respect constraints during transient animations.
CoreAnimations API's include UIView's block based animations. If you interested in further reading then here is the wwdc video
However, looking at your code, I don't think that's your problem. You want to respect layout constraints while UITabbar is animating. You just need to set your constraints in viewDidLayoutSubviews instead of videDidAppear and you should be all set with the existing code.
EDIT ---
I got some additional details about the problem. So first of all it's not an bug by apple. UITabBar is only responsible of it's direct children view controller. It's doesn't have any information about it's sub structure. You are responsible for chaining that responsibility.
The way to fix this is to listen to transitions calls of your UIViewController and animate accordingly.
This is the magic code. Add this to your view controller.
-(void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator{
NSString *prevFocusViewClassName = NSStringFromClass([context.previouslyFocusedView class]);
NSString *nextFocusedView = NSStringFromClass([context.nextFocusedView class]);
// The tabbar is going to disappear
if ([prevFocusViewClassName isEqualToString:kUITabBarButtonClassName] &&
![nextFocusedView isEqualToString:kUITabBarButtonClassName]) {
[self.view layoutIfNeeded];
[coordinator addCoordinatedAnimations:^{
[self.view layoutIfNeeded];
} completion:nil];
// The tabbar is going to appear
} else if (![prevFocusViewClassName isEqualToString:kUITabBarButtonClassName] &&
[nextFocusedView isEqualToString:kUITabBarButtonClassName]) {
[self.view layoutIfNeeded];
[coordinator addCoordinatedAnimations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
}
I have fixed your code with expected output https://www.dropbox.com/s/c8jdw367a3bnb42/SampleMovingWithTabBar-2.zip?dl=0

Layout Constraint Messages in iOS app when using layoutSubviews

This is somewhat of a followup to a previous question I posted:
Using NSLayoutConstraint on a subview in conjuction with an affine transform
I'm using the described method of placing a view I want to be rotated into another UIView, and doing the affine transform to rotate in the layoutSubviews method.
While this worked in an application having only a single UISlider to rotate, I now wish to use this method of rotation on entire UIViews, each composed of several subviews, which also have subviews, all of which use autolayout constraints to position the elements.
#implementation MMXRotateChannel
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.cstrip = [[MMXChannelStripView alloc] init];
[self addSubview:self.cstrip];
return self;
}
return nil;
}
// this works for any size of this view. the slider will always be as tall as this view is wide
- (void)layoutSubviews {
[super layoutSubviews];
// size it to be as wide as this view's height, center it in this view
self.cstrip.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width);
self.cstrip.center = [self convertPoint:self.center fromView:self.superview];
// rotate it
self.cstrip.transform = CGAffineTransformMakeRotation(M_PI * .5);
}
#end
This is actually producing the right visual result for me, but for some reason it's giving me constraint error messages, such as the following:
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSAutoresizingMaskLayoutConstraint:0x7ff932e8dae0 h=--& v=--& V:[MMXChannelStripView:0x7ff932e800d0(0)]>",
"<NSLayoutConstraint:0x7ff932e16ce0 MMXFaderView:0x7ff932e7e4d0.bottom == MMXChannelStripView:0x7ff932e800d0.bottom>",
"<NSLayoutConstraint:0x7ff932e6c150 V:|-(0)-[MMXChannelControlView:0x7ff932e1ac50] (Names: '|':MMXChannelStripView:0x7ff932e800d0 )>",
"<NSLayoutConstraint:0x7ff932e6d530 V:[MMXChannelControlView:0x7ff932e1ac50]-(0)-[MMXFaderView:0x7ff932e7e4d0]>",
"<NSLayoutConstraint:0x7ff932e6d5d0 V:[MMXFaderView:0x7ff932e7e4d0(>=500)]>",
"<NSLayoutConstraint:0x7ff932e07400 MMXChannelControlView:0x7ff932e1ac50.height >= 0.5*MMXFaderView:0x7ff932e7e4d0.height>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7ff932e6d530 V:[MMXChannelControlView:0x7ff932e1ac50]-(0)-[MMXFaderView:0x7ff932e7e4d0]>
The first constraint it lists is interesting to me, because it seems to think that I want the MMXChannelStripView to be 0 pixels, which I don't.
Note that this error message also occurs even when I don't rotate the view, but still include it as a subview using layoutSubviews. Is there some reason why what I'm doing in layoutSubviews should be incommpatible with the auto layout I'm doing at more embedded views?
I've included a screenshot to show what I'm going for. The grey view is the rotated one, and the blue one is the view which is not rotated and not enclosed in another view, and as such is not giving error messages.
I think the problem here is that you're setting the center and bounds of the MMXChannelStripView too late. By the time -[MMXRotateChannel layoutSubviews] is running, auto layout has already solved the constraint system, and it generates the error messages while solving.
Anyway, try this instead to initialize the strip:
self.cstrip = [[MMXChannelStripView alloc] init];
self.cstrip.translatesAutoresizingMaskIntoConstraints = NO;
self.cstrip.transform = CGAffineTransformMakeRotation(M_PI_2);
[self addSubview:self.cstrip];
[NSLayoutConstraint activateConstraints:#[
[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.cstrip attribute:NSLayoutAttributeWidth multiplier:1 constant:0],
[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.cstrip attribute:NSLayoutAttributeCenterX multiplier:1 constant:0],
[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.cstrip attribute:NSLayoutAttributeCenterY multiplier:1 constant:0],
]];
And you shouldn't need anything in layoutSubviews. This worked in my testing (with UISlider).
Use this instead:
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.cstrip = [[MMXChannelStripView alloc] init];
self.cstrip.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:self.cstrip];
return self;
}
return nil;
}
When programmatically instantiating views, be sure to set their translatesAutoresizingMaskIntoConstraints property to NO. By default, the system automatically creates a set of constraints based on the view’s frame and its autoresizing mask. When you add your own constraints, they inevitably conflict with the autogenerated constraints. This creates an unsatisfiable layout and hence you get those messages in the console.

ios 8 (UITableViewCell) : Constraints ambiguously suggest a height of zero for a tableview cell's content view

I have a tableview using auto layout constraints , every thing is working in iOS 7 but when i tested in iOS 8 get me the below warning
Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a tableview cell's content view. We're considering the collapse unintentional and using standard height instead.
After i made a profound investigations about this issue i found should add the below lines in viewdidload for iOS8 only
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 87;
After that still i get this warning and the height of cell isn't correct which is not take the height from Storyboard
For further info about UITableViewCell find below our constraints for content view cell
-(void) updateConstraints {
[super updateConstraints];
if(!didSetupConstraints ) {
didSetupConstraints = YES;
[self.contentView removeConstraints:self.contentView.constraints];
// Interval Title
//Leading
constraint = [NSLayoutConstraint constraintWithItem:self.intervalTitle attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterX multiplier: 1.0 constant:0.0];
[self.contentView addConstraint:constraint];
//Top
constraint = [NSLayoutConstraint constraintWithItem:self.intervalTitle attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.marketLocationTitle attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0];
[self.contentView addConstraint:constraint];
}
Auto Layout is right on that one. It's impossible to calculate cell's height from .CenterX and .Top for the label. One way to resolve the problem would be removing the existing .CenterX constraint and adding a new .Bottom constraint. That way, Auto Layout could easily calculate cell's height.

Scrolling an NSScrollView while trying to preserve the position of a subview

I have a subclass of NSScrollView. Within that scrollView's documentView I have a couple of NSViews. Finally, I add an NSView called aView to the documentView.
I want aView to be at the bottom of the documentView as long as there's no scrolling needed. Scrolling is only possible along the y-axis.
If the documentView is too high for the contentView - so that scrolling is needed - I want aView to be displayed at the bottom of the contentView.
This works fine with the code that you see below.
Here's my Problem:
The moment I start to scroll, I want aView to stay at the bottom of the contentView but aView just scrolls with all the other views within documentView.
In other words: I want aView's position to be fixed at the bottom of the visible rect of my scrollView if scrolling is needed and to stick to the bottom of the documentView if the contentView is high enough to show the whole documentView.
Is there a way to do that? Where do I go wrong?
Here's the code in my subclassed NSScrollView:
[documentView addSubview:aView];
aView.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *documentAViewBottom = [NSLayoutConstraint constraintWithItem:documentView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:aView attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
documentAViewBottom.priority = NSLayoutPriorityDefaultHigh;
[documentView addConstraint:documentAViewBottom];
NSLayoutConstraint *aViewMaxYEdge = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:aView attribute:NSLayoutAttributeBottom multiplier:1 constant:5];
[self addConstraint:aViewMaxYEdge];
[documentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[aView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(aView)]];
I am kinda oldschool and NSLayoutConstraints don't appeal to me, so here is a alternate proposed manual solution
from your subclass of NSScrollView when you set up the document view,
[self.contentView setPostsBoundsChangedNotification:YES];
then subscribe to the bounds changed
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contentViewDidScroll:)
name:NSViewBoundsDidChangeNotification object:self.contentView];
then in
-(void)contentViewDidScroll
{
double widthOfAView = aView.frame.size.width;
double heightOfAView = aView.frame.size.height;
[aView setFrameOrigin:NSMakePoint(NSMinX(self.contentView.bounds) - widthOfAView, NSMaxY(self.contentView.bounds) - heightOfAView)];
}
All of this assuming your isFlipped is overridden to YES, of course.

Interface Builder "locking" object to screen edges

I've read some tutorials on using AutoLayout but I just can't seem to figure out how to achieve something that I feel should be incredibly simple. I'm designing an application for both 3.5 and 4 inch iPhone/iPod Touch screens. It's a simple tab bar application with a UITableView filling up the entirety of each tab, like so:
I would like for the UITableView to lock to the edges of the screen, regardless of whether the screen is 3.5 or 4 inches. What I currently have works fine on 4-inch screens, but on 3.5 the UITableView extends beyond the width of the screen.
I've tried reading some AutoLayout tutorials as well as fiddling with Interface Builder constraints, but without success. I would appreciate any help you can provide.
You need to attach the UITableView to all edges of the parent UIView, and then the UITableView will expand or shrink to fill the UIView on the device. This will make it appear to be the proper size on all iDevices, including iPad. As pictured in the screenshot below, you can just tap all the dashed red guides, making sure the margins are set to 0 (touch the sides):
You could also Ctrl + drag left, drag right, drag up, and drag down on the UITableView choosing "Leading Space to Container", "Trailing Space to Container," "Top Space to Top Layout Guide", and "Bottom Space to Bottom Layout" respectively.
Alternatively, you could use the Visual Format Language (VFL) (code for a UIViewController below assumes your UITableView is an auto-#synthesized #property named tableView):
/* Turn off springs/structs */
self.tableView.translatesAutoresizingMaskIntoConstraints = NO;
/* Leading and trailing constraints */
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_tableView]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_tableView)]];
/* Top and bottom constraints */
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_tableView]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_tableView)]];
...or if you really like to be explicit (and like typing):
/* Leading constaint (could use NSLayoutAttributeLeft here as well) */
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.tableView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0]];
/* Trailing constraint (could use NSLayoutAttributeRight here as well) */
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.tableView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0]];
/* Top constraint */
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.tableView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]];
/* Bottom constraint */
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.tableView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]];
The important thing with all of this is that the UITableView is a child of the view of the UIViewController (it most likely is). view will fill the screen as expected by default, and with all of the methods above, you're asking the layout to hold tight against the edges of that maximized view.
NSLayoutConstraints is ridiculous overkill for this IMHO.
Just turn it off and do this
self.tableview.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;