I have a collection view pinned to its superview bottom, left, and right edges and a text field pinned to superview top, left, and right. The collection view's height is increased in updateConstraints from the bottom up with a height constraint based on its content size as more items are added to it.
I'd like the collection view height to grow but not exceed the
bottom of the textField above it with an inequality constraint on the
collection view's top edge. Here's what I've tried:
self.topResistanceConstraint = [NSLayoutConstraint constraintWithItem:self.collectionView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:self.textField
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0];
[self addConstraint:self.topResistanceConstraint];
CGSize size = self.collectionView.collectionViewLayout.collectionViewContentSize;
self.heightConstraint = [NSLayoutConstraint constraintWithItem:self.collectionView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:size.height];
[self addConstraint:self.heightConstraint];
This results in unsatisfiable constraints, and recovers (and actually appears to work), but it's not a good solution and doesn't work if the layout needs to adapt to a keyboard appearing or disappearing. Any suggestions how to create constraints or priorities to get this to work without breaking constraints?
After some trial and error, simply setting the heightConstraint to low priority works well. The topResistanceConstraint's priority is required, and prevents the collection view's top from exceeding the bottom of the text field.
self.heightConstraint = [NSLayoutConstraint constraintWithItem:self.collectionView attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:size.height];
// set a low priority on height
self.heightConstraint.priority = UILayoutPriorityLow;
[self addConstraint:self.heightConstraint];
The preferred solution for this problem is to create a height and top constraint
(top constrains related by "NSLayoutRelationGreaterThanOrEqual") for the collectionView in the storyboard and create the outlet for both.
After that get the collectionView item count and than dynamically calculate the height of the collectionView as below
NSInteger count = 5;
CGFloat collectionViewHeight = count/2 * cellHeight; // for collectionView with two(2) columns
CGFloat maxCollectionViewHeight = CGRectGetHeight(self.view.frame) - CGRectGetHeight(self.textField.frame) - CGRectGetMaxY(self.textField.frame);
if (collectionViewHeight < maxCollectionViewHeight ) {
self.heightConstraint.constant = collectionViewHeight;
} else {
self.topConstraint.constant = 20; // Min spacing between two views;
}
Related
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.
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.
I would like ButtonA to stay where it is, and ButtonB to move a distance x vertically downward away from it.
// Using Autolayout.
UIButton *buttonA, *buttonB;
UIView *fatherView; // The superview of buttonA and B.( The view where they are inside).
CGFloat distance;
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:buttonB attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:buttonA attribute:NSLayoutAttributeBottom multiplier:1 constant:distance];
[fatherView addConstraint:constraint];
// Without autolayout, maybe more easy.
buttonB.center = CGPointMake(buttonA.center.x, buttonA.center.y + distance);
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.
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.