iOS7 Autolayout Issues - Adding UIView Subview to UITableViewController - objective-c

I have been trying for a few hours. I am adding a subclass of UIView to the view of my UITableViewController. I am using the code at the bottom of the this post.
I get a crash that says:
2014-06-15 12:19:58.724 Block Party[4712:60b] *** Assertion failure in -[UITableView layoutSublayersOfLayer:], /SourceCache/UIKit/UIKit-2935.137/UIView.m:8794
2014-06-15 12:19:58.726 Block Party[4712:60b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super.'
*** First throw call stack:
(0x2e940f0b 0x390d7ce7 0x2e940ddd 0x2f2ede2f 0x311703c5 0x30dec31b 0x30de7b3f 0x311854a1 0x3e387 0x311a5a3b 0x311bb8a5 0x3e221 0x3d267 0x31174a53 0x31174811 0x31300c13 0x3121e48f 0x3121e299 0x3121e231 0x31170305 0x30dec31b 0x30de7b3f 0x30de79d1 0x30de73e5 0x30de71f7 0x30de0f1d 0x2e90c039 0x2e9099c7 0x2e909d13 0x2e874769 0x2e87454b 0x337e16d3 0x311d3891 0x3acb5 0x395d5ab7)
libc++abi.dylib: terminating with uncaught exception of type NSException
I attempted to resolve this by adding the layoutSublayerofLayer method but it didn't change anything. Also, if I comment out [self.locationNeededView setTranslatesAutoresizingMaskIntoConstraints:NO]; then it doesn't crash but my constraints are ignored because the autoresizingmaskconstraints take priority.
I'm totally out of ideas for how to solve this. Basically I want this view to appear on top of my table, centered horizontally and vertically.
Thanks in advance for any help you can offer.
-Jeff
- (void) layoutSublayersOfLayer:(CALayer *)layer {
[super layoutSublayersOfLayer:layer];
}
- (void)loadLocationNeededView {
UINib *nib = [UINib nibWithNibName:#"LocationNeededUIView" bundle:nil];
self.locationNeededView = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
[self.locationNeededView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem: self.view
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.locationNeededView
attribute:NSLayoutAttributeCenterX
multiplier:0
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem: self.view
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.locationNeededView
attribute:NSLayoutAttributeCenterY
multiplier:0
constant:0]];
[self.view addSubview:self.locationNeededView];
[self.view layoutSubviews];
}
Edit - Update
I gave up on using constraints and I'm just using the frame. I got it to work pretty well with the following code. I also found if I add it to the view of the navigation controller, then it won't scroll with the tableview. Which is exactly what I wanted.
- (void)loadLocationNeededView {
//allocate subview
UINib *nib = [UINib nibWithNibName:#"LocationNeededUIView" bundle:nil];
self.locationNeededView = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
//set frame for it offscreen so it can be animated in
self.locationNeededView.frame = CGRectMake((self.view.frame.size.width / 2) - (self.locationNeededView.frame.size.width / 2), [[UIScreen mainScreen] bounds].size.height, self.locationNeededView.frame.size.width, self.locationNeededView.frame.size.height);
//addsubview to navigation controller so that it does not scroll with the tableview
[self.navigationController.view addSubview:self.locationNeededView];
[self animateLocationNeededViewIn];
}
- (void) animateLocationNeededViewIn {
[UIView animateWithDuration:0.35
delay:1
usingSpringWithDamping:0.7
initialSpringVelocity:1.0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^ {
self.locationNeededView.frame = CGRectMake((self.view.frame.size.width / 2) - (self.locationNeededView.frame.size.width / 2), (self.view.frame.size.height / 2) - (self.locationNeededView.frame.size.height / 2), self.locationNeededView.frame.size.width, self.locationNeededView.frame.size.height);
[self.view layoutIfNeeded];} completion:^ (BOOL fin) {
if (fin) {
NSLog(#"After Animating View Frame: Origin X = %f. Origin Y = %f. Width = %f. Height = %f", self.locationNeededView.frame.origin.x, self.locationNeededView.frame.origin.y, self.locationNeededView.frame.size.width, self.locationNeededView.frame.size.height);
}
}];
}

Issues: Constraint Multipliers should be 1, not 0. also, you shouldn't need to call layoutSubviews, just call layoutIfNeeded. Also your view needs width and height constraints unless you have defined intrinsiccontentSize in your view.

Related

programmatic NSLayoutConstraint in Mac app resizes window contentView

I'm trying to get auto layout working programmatically in an Objective-C Mac app.
The goal pretty basic, a simple toolbar-like view across the top of the window. Height should not change, view should stay at top of window, and width should resize with the window.
When I configure this in the .xib it works great. Without worrying about height, I just add leading, trailing, and top constraint connections from the custom view to the superview (which is the NSWindow's contentView). I don't have to change any priorities or other settings.
I need to do this programmatically, creating window, subview, and constraints in code. I've tried using both anchors and NSLayoutConstraint objects (both parameterized and based on visual layout string). In all cases I get a bizarre behavior where the window's contentView resizes, down to height zero, and nothing is drawn to the screen.
Here's one complete approach, using anchors:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// make window
NSRect windowRect = NSMakeRect(0.0, 0.0, 900.0, 200.0);
NSWindowStyleMask windowStyle = NSWindowStyleMaskTitled
| NSWindowStyleMaskClosable
| NSWindowStyleMaskResizable
| NSWindowStyleMaskMiniaturizable;
NSWindow * win = [[NSWindow alloc] initWithContentRect:windowRect
styleMask:windowStyle
backing:NSBackingStoreBuffered
defer:NO];
win.title = #"Window Made in Code";
win.contentView.translatesAutoresizingMaskIntoConstraints = NO;
// add view
NSRect viewFrame = windowRect;
viewFrame.size.height = 80.0;
viewFrame.origin.y = 120.0;
NSView * view = [[MyView alloc] initWithFrame:viewFrame];
[win.contentView addSubview:view];
[view setNeedsDisplay:YES];
// add constraints
[view.leadingAnchor constraintEqualToAnchor:win.contentView.leadingAnchor].active = YES;
[view.trailingAnchor constraintEqualToAnchor:win.contentView.trailingAnchor].active = YES;
[view.topAnchor constraintEqualToAnchor:win.contentView.topAnchor].active = YES;
// note, the above also tried using layoutMargins of superview, same behavior
// show window
[win setIsVisible:YES];
[win center];
self.codeWindow = win;
[self.codeWindow makeKeyAndOrderFront:NSApp];
}
If I don't add the constraints, everything draws correctly, but as expected the subview does not resize with the window.
When I add the constraints, the superview (window's contentView) gets resized to height 0, so nothing gets drawn. If I omit the top constraint this does not happen, however the horizontal constraints then work backward: rather than resizing subview as the window is resized, they prevent the Window contentView from resizing horizontally; I can still drag the window larger but debugging see that the content view frame width does not increase.
Here's an alternate code approach I also tried using NSLayoutConstraint factory methods; here I'm setting priority but no different priority corrects the behavior:
NSLayoutConstraint * leadingC = [NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:view.superview
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0.0];
leadingC.priority = NSLayoutPriorityDragThatCannotResizeWindow;
NSLayoutConstraint * trailingC = [NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:view.superview
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0.0];
trailingC.priority = NSLayoutPriorityDragThatCannotResizeWindow;
NSLayoutConstraint * topC = [NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:view.superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0];
[NSLayoutConstraint activateConstraints:#[leadingC, trailingC, topC]];
[view.superview addConstraints:self.constraints];
Since I'm getting the same bad result trying this several different ways I feel like I must be completely missing something with NSLayoutConstraint, but I haven't been able to figure it out from tutorials or documentation. I did compare via debugging layout constraints at runtime between .xib and code-created windows but haven't been able to reconstruct what I'm doing wrong from the differences, except that the .xib superview has NSAutoresizingMaskLayoutConstraint objects.
If I comment-out win.contentView.translatesAutoresizingMaskIntoConstraints = NO; I do get resizing behavior programmatically, but that is confusing to me - I thought when we created explicit constraints we were supposed to set that flag to NO?
Thanks for any suggestions on how to get this working!
When you opt into constraints, you have to completely describe the size of the view with the constraints. In this case, you've specified the width of your toolbar, and you set it's position, but it looks like you are trying to fix the height with the frame you initially give the view. You also have to have a constraint to set its height.
Also translatesAutoresizingMaskIntoConstraints describes how a view relates to its superview, not its child views. So you should be setting that on the toolbar view, not on the contentView of the window. Try this code:
#import "AppDelegate.h"
#interface AppDelegate ()
#end
#interface MyView : NSView
#end
#implementation MyView
- (void)drawRect:(NSRect)dirtyRect
{
CGContextRef cgContext = [[NSGraphicsContext currentContext] CGContext];
CGContextSetFillColorWithColor(cgContext, [[NSColor yellowColor] CGColor]);
CGContextFillRect(cgContext, self.bounds);
}
#end
#implementation AppDelegate
{
NSWindowController *disjointController;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSRect windowRect = NSMakeRect(0.0, 0.0, 900.0, 200.0);
NSWindowStyleMask windowStyle = NSWindowStyleMaskTitled
| NSWindowStyleMaskClosable
| NSWindowStyleMaskResizable
| NSWindowStyleMaskMiniaturizable;
NSWindow * win = [[NSWindow alloc] initWithContentRect:windowRect
styleMask:windowStyle
backing:NSBackingStoreBuffered
defer:NO];
win.title = #"Window Made in Code";
// add view
NSView *view = [[MyView alloc] initWithFrame: windowRect];
view.translatesAutoresizingMaskIntoConstraints = NO;
view.layer.backgroundColor = [[NSColor yellowColor] CGColor];
[win.contentView addSubview: view];
// add constraints
[view.leadingAnchor constraintEqualToAnchor: win.contentView.leadingAnchor].active = YES;
[view.trailingAnchor constraintEqualToAnchor: win.contentView.trailingAnchor].active = YES;
[view.topAnchor constraintEqualToAnchor: win.contentView.topAnchor].active = YES;
[view.heightAnchor constraintEqualToConstant: 80].active = YES;
// show window
[win center];
disjointController = [[NSWindowController alloc] initWithWindow: win];
[win makeKeyAndOrderFront:NSApp];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
return YES;
}
#end

Adding Autolayout programatically to existing autolayout Views added in xibs

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.

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.

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.