I have following problem: I have created a custom UIView class and its layout in XIB file. Let's say that size of my custom view in XIB is 150 x 50. I have enabled sizeClasses (wAny hAny) and AutoLayout. In Simulated Metrics I have set Size = Freeform, Status Bar = None, all other = Inferred.
Code from my custom UIView class looks like this:
#import "CustomView.h"
#implementation CustomView
#pragma mark - Object lifecycle
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self setup];
}
return self;
}
#pragma mark - Private & helper methods
- (void)setup {
if (self.subviews.count == 0) {
[[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:self options:nil];
self.bounds = self.view.bounds;
[self addSubview:self.view];
}
}
#end
In my UIViewController where I want to add this custom UIView, I have made dummy UIView (which size I have set in Interface Builder) where I want to add my CustomView and I would like that my CustomView fits the bounds of my container view.
I am trying to do that like this:
_vCustomView = [[CustomView alloc] init];
_vCustomView.translatesAutoresizingMaskIntoConstraints = NO;
[_vContainer addSubview:_vCustomView];
[_vContainer addConstraint:[NSLayoutConstraint constraintWithItem:_vCustomView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:_vContainer attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0]];
[_vContainer addConstraint:[NSLayoutConstraint constraintWithItem:_vCustomView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:_vContainer attribute:NSLayoutAttributeRight multiplier:1.0 constant:0.0]];
[_vContainer addConstraint:[NSLayoutConstraint constraintWithItem:_vCustomView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:_vContainer attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0]];
[_vContainer addConstraint:[NSLayoutConstraint constraintWithItem:_vCustomView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:_vContainer attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0]];
But no luck. Let's say that my container view is 300 x 100, when I add my custom UIView to it, my custom view is still 150 x 50.
I have tried to work without container view - to add my custom UIView object directly to self.view of my UIViewController and set it's width and height with:
autoLayout constraints
by using initWithFrame when initializing my custom UIView
but again - no luck. Custom view is always 150 x 50.
Can someone explain to me how can I programmatically add my custom UIView made in XIB and resize it using autoLayout constraints?
Many thanks in advance.
Edit #1: Error I get when trying to run Andrea's code on my CustomView
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)
(
"<NSLayoutConstraint:0x17489b760 V:[UIView:0x17418d820(91)]>",
"<NSLayoutConstraint:0x170c9bf80 V:|-(0)-[CustomView:0x1741da7c0] (Names: '|':CustomView:0x1741da8b0 )>",
"<NSLayoutConstraint:0x170c9bfd0 V:[CustomView:0x1741da7c0]-(0)-| (Names: '|':CustomView:0x1741da8b0 )>",
"<NSLayoutConstraint:0x170c9be90 V:|-(0)-[CustomView:0x1741da8b0] (Names: '|':UIView:0x17418d820 )>",
"<NSLayoutConstraint:0x170c9bee0 CustomView:0x1741da8b0.bottom == UIView:0x17418d820.bottom>",
"<NSAutoresizingMaskLayoutConstraint:0x170c9dba0 h=--& v=--& CustomView:0x1741da7c0.midY == + 25.0>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x170c9bfd0 V:[CustomView:0x1741da7c0]-(0)-| (Names: '|':CustomView:0x1741da8b0 )>
Edit #2: It works!
After #Andrea added:
view.translatesAutoresizingMaskIntoConstraints = NO;
in his:
- (void) stretchToSuperView:(UIView*) view;
method, everything works like I expected.
Thanks to #Andrea.
I guess that the main issue is that you do not use auto layout when you add your xib into the content view.
After adding the subview in you init methods please call that method, passing the view of the xib:
- (void) stretchToSuperView:(UIView*) view {
view.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *bindings = NSDictionaryOfVariableBindings(view);
NSString *formatTemplate = #"%#:|[view]|";
for (NSString * axis in #[#"H",#"V"]) {
NSString * format = [NSString stringWithFormat:formatTemplate,axis];
NSArray * constraints = [NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:nil views:bindings];
[view.superview addConstraints:constraints];
}
}
[EDIT]
Your setup code should look like that:
- (void)setup {
if (self.subviews.count == 0) {
[[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:self options:nil];
self.bounds = self.view.bounds;
[self addSubview:self.view];
[self stretchToSuperView:self.view];
}
}
Pay attention that inside stretch view I've added view.translatesAutoresizingMaskIntoConstraints = NO;
[EDIT 2]
I'll try to elaborate my answer as requested. Using autolayout, when you add a view without setting constraints autolayout automatically converts autoresizing masks into constraints the default property of them is UIViewAutoresizingNone that means to do not autoresize.
Your XIB view was added to its parent without constraints and without the possibility of autoresize thus keeping its original size. Since you want that your view to resize accordingly to your view you had two choices:
Change the autoresizing masks of your xib view to flexible width and height but they need to match the parent size or you will not have a full cover
Add some constraints that constraint the two view to change accordingly to the parent changes. And you achieve that saying between the XIB view and its parent view the space between trailing/leading and top/botton is 0. Is like you are putting some glue on their borders. To do that you need to set the autotranslate resizing mask into constraint to NO, or you can have some conflicts as you posted in the log.
What you are doing later is adding constraints to the view (that hosts the XIB view) to its superview, but there were no constraints between the XIB and its parent, thus autolayout doesn't know how to resize the XIB view. Each views in a view hierarchy should have its own constraints. Hope this helps to understand better.
Swift 4.0 function to stretch to super view:
Code Snippet:
static public func stretchToSuperView(view:UIView)
{
view.translatesAutoresizingMaskIntoConstraints = false
var d = Dictionary<String,UIView>()
d["view"] = view
for axis in ["H","V"] {
let format = "\(axis):|[view]|"
let constraints = NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(rawValue: 0), metrics: [:], views: d)
view.superview?.addConstraints(constraints)
}
}
Note: Just call the function with an argument as your subview.
This helps me to resolve the .xib resing issue in swift 4.0.
In your code you should have something like this:
First, a IBOutlet to the width constraint you wanna change, either in the viewController or in your CustomView (I don't really know where you have the logic to calculate the constraint value).
#property (nonatomic, weak) IBOutlet NSLayoutConstraint *widthConstraint;
Then, in the method you wanna update the constant value, you'll have something like this:
-(void)updateWidth {
[self.widthConstraint setConstant:newConstant];
[self.view layoutIfNeeded]; // self.view must be an ancestor of the view
//If you need the changes animated, call layoutIfNeeded in an animation block
}
Hope this works for you. Good luck!
Related
i use autolayout without sb and xib.But i have some problems recently.
i don't understand which view should i use to implement the following two methods
- (void)addConstraint:(NSLayoutConstraint *)constraint
- (void)addConstraints:(NSArray<__kindof NSLayoutConstraint *> *)constraints
for example,i have a super view as following:
_menuView = [[UIScrollView alloc] init];
_menuView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:_menuView];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[_menuView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_menuView)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[_menuView(40)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_menuView)]];
and two subViews:view1,view2.When i use autolayout to add constraints to describe view1 and view2,
[WHICHVIEW addConstraint:<#(nonnull NSLayoutConstraint *)#>];
or
[WHICHVIEW addConstraints:<#(nonnull NSArray<__kindof NSLayoutConstraint *> *)#>];
what should WHICHVIEW should be?
Let me put it another way,is WHICHVIEW depend on the relationship between view1 and view2?what if view1 is not the same hierarchy as view2?
WHICHVIEW should be a parent of [all] the items you are trying to constrain. So if you are adding a number of views to a scrollview and trying to constrain them, with respect to themselves and to the scrollview, the scrollview should be WHICHVIEW. If view1 and view2 are not at all in the same hierarchy, then you cannot add a constraint between the two.
Do not use any WHICHVIEW. Do not call addConstraints:. Call NSLayoutConstraint.activateConstraints instead. It has the advantage that it does all that work for you - it adds the constraints to the correct views, automatically.
I am confused with the autolayout on xib/nib. I am comfortable with storyboard autolayout.
What I am trying to do is:
1. I have a storyboard with one view controller with one UIView called borderView. It is constrained correctly.It is resized correctly for iphone 5 and 6. Here is the screenshot of the storyboard:
This is the borderView in iphone 5: (I am trying to add the nib view as a subview to this border view)
2. I created a nib with UIView in it with the dimensions 300 x 300. I want this view to be added to the borderVIew in my ViewController. HEre is the screenshot for my nib.
Note: I didnt gave any height or width constraints anywhere. I just gave leading ,traliing,top and bottom.
and I am trying to add the nib to my borderview as follows:
This is the method in my viewController:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[[NSBundle mainBundle] loadNibNamed:#"AlertView" owner:self options:nil];
[self.borderView addSubview:self.myAlertViewFromNib];
}
and the result in the iphone 5 is:
As you can see, the nib view is not aligned in the center of the screen (as the border view is aligned center to the screen).
I have no idea of how to give constraints to nib itself. Can anyone please tell me if it is possible to do it in xcode or do I need to give the constraints programmatically?
You want to add the AlertView as a center aligned in the view controller
Rather than setting size from the xib just create the perfect constrained XIB view and define macros for sizes as below.
Step 1 have some macros for size
#define kAlertViewWidth 300
#define kAlertViewHeight 300
Step 2 in #interface .Please create on property like this in the viewcontroller
#property (nonatomic, retain) IBOutlet UIView *myViewFromNib;
Getter Method For the property
-(UIView *)myViewFromNib
{
if (!_myViewFromNib) {
_myViewFromNib = [[[NSBundle mainBundle] loadNibNamed:#"AlertView" owner:self options:nil] objectAtIndex:0];
_myViewFromNib.translatesAutoresizingMaskIntoConstraints = NO;
}
return _myViewFromNib;
}
//Step 3 in viewDidLoad method
-(void)viewDidLoad{
[self setUpConstraints];
}
Method which will add the required constraints
-(void)setUpConstraints
{
\[self.view addSubview:self.myViewFromNib\];
\[NSLayoutConstraint activateConstraints:#\[\[NSLayoutConstraint constraintWithItem:self.myViewFromNib attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0\],
\[NSLayoutConstraint constraintWithItem:self.myViewFromNib attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0\],
\[NSLayoutConstraint constraintWithItem:self.myViewFromNib attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0
constant:kAlertViewWidth\],
\[NSLayoutConstraint constraintWithItem:self.myViewFromNib attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0
constant:kAlertViewHeight\]\]\];
}
If you have no choice but to do it this way try setting the centre manually. i.e. yourView.frame.x = super.frame.size.width/2 - yourView.frame.size.width/2. This should centre it horizontally. If it won't let you, then you'd have to add in the constraints after adding in to subview.
Instead of a xib and a storyboard, use a storyboard and inside your borderview add another view (with everything you have in the xib) and constrain that view within your borderview.
Use the vertically center and horizontally center constraints to center the inner view. If you need to change the constraints later, create an outlet for them in your header file and adjust the priority.
If you still want to use a xib within a view, try using a third party library like Masonry to adjust your xib.
I have a UITableViewSubclass (style: UITableViewCellStyleValue1) that has a custom label that is essentially a replacement for the normal textLabel label. It should be left aligned with the normal textLabel but has a width to end 8 points to the left of the normal detailTextLabel. I get the desired effect, but when the cells are used, an exception is thrown about being unable to satisfy the constraints simultaneously.
What I don't understand is why it complains, there is no apparent conflict. Calling [self layoutIfNeeded] after setting up the constraints silences the exception. Alternatively, lowering the priority of the constraint that configures the trailing property (for example, UILayoutPriorityDefaultHigh instead of the default UILayoutPriorityRequired) also silences the exception, obviously because I'm informing the Auto Layout engine that it's okay to break/ignore that constraint.
I'm assuming that the standard UITableView implementation maybe oddly lays out the textLabel and textLabel in a fashion that makes it initially impossible to describe the view described. For example, if the textLabel was actually placed on the right side of the cell and detailTextLabel was placed on the left side, then the layout I've described would be impossible.
When does Auto Layout take effect, in the context of this scenario. Is it jumping the gun and attempting to layout things before it's supposed to?
No Storyboards or XIBs are being used. Purely code based.
#import "EBTStoreTableViewCell.h"
#interface EBTStoreTableViewCell ()
#property (nonatomic, readonly, weak) UILabel *storeNameLabel;
#end
#implementation EBTStoreTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier];
if (self) {
self.textLabel.text = #" ";
self.detailTextLabel.text = #" ";
UILabel *storeNameLabel = [[UILabel alloc] init];
storeNameLabel.translatesAutoresizingMaskIntoConstraints = NO;
[storeNameLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
[self.contentView addSubview:storeNameLabel];
_storeNameLabel = storeNameLabel;
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:storeNameLabel attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.textLabel attribute:NSLayoutAttributeLeading multiplier:1.0f constant:0.0f]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:storeNameLabel attribute:NSLayoutAttributeBaseline relatedBy:NSLayoutRelationEqual toItem:self.textLabel attribute:NSLayoutAttributeBaseline multiplier:1.0f constant:0.0f]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:storeNameLabel attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.detailTextLabel attribute:NSLayoutAttributeLeading multiplier:1.0f constant:-8.0f]];
// [self layoutIfNeeded]; // doing this makes the issue go away
}
return self;
}
// Setters ommited
#end
This the exception message I get:
2014-04-23 11:50:42.092 Application[32507:60b] 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)
(
"<NSLayoutConstraint:0xde03910 UILabel:0xde036b0.leading == UILabel:0xde00590.leading>",
"<NSLayoutConstraint:0xde03a30 UILabel:0xde036b0.trailing == UITableViewLabel:0xde00c10.leading - 8>",
"<NSAutoresizingMaskLayoutConstraint:0xde01920 h=--& v=--& UILabel:0xde00590.midX ==>",
"<NSAutoresizingMaskLayoutConstraint:0xde1de50 h=--& v=--& H:[UILabel:0xde00590(0)]>",
"<NSAutoresizingMaskLayoutConstraint:0x17f50a10 h=--& v=--& UITableViewLabel:0xde00c10.midX ==>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0xde03a30 UILabel:0xde036b0.trailing == UITableViewLabel:0xde00c10.leading - 8>
Break on objc_exception_throw to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
You shouldn't mix and match using the textLabel and detailTextLabel. These can not be constrained as their size are managed internally and might produce unexpected results.
You should create your own labels, and then places constraints on them.
From the docs for UITableViewCell:
When creating cells, you can customize them yourself or use one of several predefined styles. The predefined cell styles are the simplest option. With the predefined styles, the cell provides label and image subviews whose positions and styling are fixed. All you have to do is provide the text and image content to go into those fixed views. To use a cell with a predefined style, initialize it using the initWithStyle:reuseIdentifier: method or configure the cell with that style in Xcode. To set the text and images of the cell, use the textLabel, detailTextLabel, and imageView properties.
If you want to go beyond the predefined styles, you can add subviews to the contentView property of the cell. When adding subviews, you are responsible for positioning those views and setting their content yourself.
I'm building a complicated project where, among other things, I need to set a UIPageViewController as a childview of a main view. I'm using autolayout, and using constraints to order the various elements on the main view.
The problem is, that when I try to run the app, it crashes due to conflicting NSAutoresizingMaskLayoutConstraints.
2013-10-28 16:22:18.419 Red Event App[1658:60b] 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)
(
"<NSLayoutConstraint:0x145c1990 V:|-(20)-[UINavigationBar:0x145bf6b0] (Names: '|':UIView:0x145bf620 )>",
"<NSLayoutConstraint:0x145bf510 V:[UINavigationBar:0x145bf6b0]-(0)-[UIView:0x145bef70]>",
"<NSLayoutConstraint:0x145d0550 UIView:0x145a8c10.top == UIView:0x145bf620.top>",
"<NSAutoresizingMaskLayoutConstraint:0x145b3a40 h=-&- v=-&- UIView:0x145a8c10.midY == UIView:0x145bef70.midY + 56.5>",
"<NSAutoresizingMaskLayoutConstraint:0x145b3a70 h=-&- v=-&- UIView:0x145a8c10.height == UIView:0x145bef70.height + 113>"
)
The usual cure for this is setting TranslatesAutoresizingMaskIntoConstraints to no.
However, when I do this the child views of the UIPageViewController start to ignore the bounds of the PageViewController, and end up (as far as I can see) with an origin of (0,0).
I've tried to fix the position by setting the frames by hand both when setting up the datasource (_itemViewControllers):
- (void)setupLeafs{
_itemViewControllers = [[NSMutableArray alloc]init];
for(NSString *pagename in _datasource){
RWNode *page = [_xml getPage:pagename];
UIViewController *viewController = [RWNavigationController getViewControllerFromDictionary:[page getDictionaryFromNode]];
viewController.view.frame = CGRectMake(self.view.frame.origin.x,self.view.frame.origin.y,self.view.frame.size.width, self.view.frame.size.height);
[_itemViewControllers addObject:viewController];
}
}
and when getting the page
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
UIViewController *nextViewController = [self getPreviousLeaf:viewController];
nextViewController.view.frame = CGRectMake(self.view.frame.origin.x,self.view.frame.origin.y,self.view.frame.size.width, self.view.frame.size.height);
return nextViewController;
}
but neither have any effect.
I need the constraints for what I'm doing, so sticking to Masks is not an option. I think what I'm looking for is a way to put constraints on the UIPageViewController children after they've been added (by whatever process calls viewControllerBeforeViewController). But I'd really like to hear about any way that this problem can be solved.
Edit:
I have found a hack to solve the problem. I'm not quite sure if what I'm listing here is the entire solution, but it is what I notice right now, after more than a month of tinkering with the code.
First, in the view controller that sets up the pageviewcontroller, I have the following two lines, after I've initialized the pageviewcontroller
UIView *pageView = self.pageViewController.view;
pageView.frame = self.view.frame;
Not that I have set [self.view setTranslatesAutoresizingMaskIntoConstraints:NO]; in this view controller.
Secondly, on the child controllers I check whether the view is being used inside or outside of a pageviewcontroller. Only if the view is not being used in a pageviewcontroller is [self.view setTranslatesAutoresizingMaskIntoConstraints:NO]; set on the child view.
Now, this works (for me) at the moment. But I would really like a solution that is less hacky.
When using AutoLayout, you should never directly set the frame of a view. Constraints are used to do this for you. Normally if you want to set your own constraints in a view, you override the updateConstraints method of your UIViews. Make sure the content views for the page controller allow for their edges to be resized since they will be sized to fit the page view's frame. Your constraints and view setup will need to account for this, or you you will get unsatisfiable constraint errors.
While I don't know exactly what you are trying to achieve visually, here are two pieces of advice that might help you:
Firstly, remember to remove all existing constraints from a UIView before adding new ones in code. Don't mix constraints from Interface Builder with constraints in code, it will drive you insane. Example:
- (void)viewDidLoad
{
[super viewDidLoad];
// remove all constraints
[self.view removeConstraints:self.view.constraints];
// add new constraints
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[webView]|"
options:0
metrics:nil
views:#{#"webView": self.webView}]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[webView]|"
options:0
metrics:nil
views:#{#"webView": self.webView}]];
}
Secondly, you can have NSLayoutConstraint outlets, just like any other outlets from a XIB or a Storyboard. That way, you can adjust certain properties of a constraint at runtime:
// in your header:
#property (nonatomic, weak) IBOutlet NSLayoutConstraint *myConstraint;
// somewhere in your code where you need to adjust the constraint:
self.myConstraint.constant = 100;
I hope this helps a little bit :)
I can't give you the answer, but perhaps I can give you some debug advice.. copy the log information into a text editor and replace the memory address information for the views with meaningful names. (use the debugger to find which properties the memory addresses map to if you can't figure it out). Then you will find it a bit easier to understand the log information and trace the conflict. Here is an example (although i'm just guessing your view hierarchy)
"<NSLayoutConstraint:0x145c1990 V:|-(20)-[UINavigationBar:NAVBAR] (Names: '|':UIView:ROOTVIEW )>",
"<NSLayoutConstraint:0x145bf510 V:[UINavigationBar:NAVBAR]-(0)-[UIView:PAGECONTAINER]>",
"<NSLayoutConstraint:0x145d0550 UIView:PAGEVIEW.top == UIView:ROOTVIEW.top>",
"<NSAutoresizingMaskLayoutConstraint:0x145b3a40 h=-&- v=-&- UIView:PAGEVIEW.midY == UIView:PAGECONTAINER.midY + 56.5>",
"<NSAutoresizingMaskLayoutConstraint:0x145b3a70 h=-&- v=-&- UIView:PAGEVIEW.height == UIView:PAGECONTAINER.height + 113>"
At a guess, the last two autoresizingmasklayoutconstraints look strange to me, they don't look clearly defined.
I just ran into a similar situation. If I set any constraints on the UIPageViewController's view, it would cause conflicts with the autoresizing mask constraints.
What works for me is to set translatesAutoresizingMaskIntoConstraints to NO for UIPageController view, then add constraints to the parent view for the pageController view's top, left, width, and height.
self.pageController.view.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:self.pageController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0];
NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:self.pageController.view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0];
NSLayoutConstraint *constraint3 = [NSLayoutConstraint constraintWithItem:self.pageController.view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:1.0 constant:-60.0];
NSLayoutConstraint *constraint4 = [NSLayoutConstraint constraintWithItem:self.pageController.view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0];
[self.view addConstraints:#[constraint1, constraint2, constraint3, constraint4]];
I am configuring a custom UITableViewCell using a prototype cell in a Storyboard. However, all the UILabels (and other UI elements) do not seem to be added to the cell's contentView, instead being added to the UITableViewCell view directly. This creates issues when the cell is put into editing mode, as the content is not automatically shifted/indented (which it would do, if they were inside the contentView).
Is there any way to add the UI elements to the contentView when laying out the cell using Interface Builder/Storyboard/prototype cells? The only way I have found is to create everything in code and use [cell.contentView addSubView:labelOne] which wouldn't be great, as it is much easier to layout the cell graphically.
On further investigation (viewing the subview hierarchy of the cell) Interface Builder does place subviews within the cell's contentView, it just doesn't look like it.
The root cause of the issue was iOS 6 autolayout. When the cell is placed into editing mode (and indented) the contentView is also indented, so it stands to reason that all subviews within the contentView will move (indent) by virtue of being within the contentView. However, all the autolayout constraints applied by Interface Builder seem to be relative to the UITableViewCell itself, rather than the contentView. This means that even though the contentView indents, the subviews contained within do not - the constraints take charge.
For example, when I placed a UILabel into the cell (and positioned it 10 points from the left-hand side of the cell) IB automatically applied a constraint "Horizontal Space (10)". However, this constraint is relative to the UITableViewCell NOT the contentView. This means that when the cell is indented, and the contentView moves, the label stays put as it is complying with the constraint to remain 10 points from the left-hand side of the UITableViewCell.
Unfortunately (as far as I am aware) there is no way to remove these IB created constraints from within IB itself, so here is how I solved the problem.
Within the UITableViewCell subclass for the cell, I created an IBOutlet for that constraint called cellLabelHSpaceConstraint. You also need an IBOutlet for the label itself, which I called cellLabel. I then implemented the -awakeFromNib method as per below:
- (void)awakeFromNib {
// -------------------------------------------------------------------
// We need to create our own constraint which is effective against the
// contentView, so the UI elements indent when the cell is put into
// editing mode
// -------------------------------------------------------------------
// Remove the IB added horizontal constraint, as that's effective
// against the cell not the contentView
[self removeConstraint:self.cellLabelHSpaceConstraint];
// Create a dictionary to represent the view being positioned
NSDictionary *labelViewDictionary = NSDictionaryOfVariableBindings(_cellLabel);
// Create the new constraint
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-10-[_cellLabel]" options:0 metrics:nil views:labelViewDictionary];
// Add the constraint against the contentView
[self.contentView addConstraints:constraints];
}
In summary, the above will remove the horizontal spacing constraint which IB automatically added (as is effective against the UITableViewCell rather than the contentView) and we then define and add our own constraint to the contentView.
In my case, all the other UILabels in the cell were positioned based upon the position of the cellLabel so when I fixed up the constraint/positioning of this element all the others followed suit and positioned correctly. However, if you have a more complex layout then you may need to do this for other subviews as well.
As mentioned, XCode's Interface Builder is hiding the UITableViewCell's contentView. In reality, all UI elements added to the UITableViewCell are in fact subviews of the contentView.
For the moment, it IB is not doing the same magic for layout constraints, meaning that they are all expressed at UITableViewCell level.
A workaround is in a subclass's awakeFromNib to move all NSAutoLayoutConstrains from UITableViewCell to it's contentView and express them in terms of the contentView :
-(void)awakeFromNib{
[super awakeFromNib];
for(NSLayoutConstraint *cellConstraint in self.constraints){
[self removeConstraint:cellConstraint];
id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
NSLayoutConstraint* contentViewConstraint =
[NSLayoutConstraint constraintWithItem:firstItem
attribute:cellConstraint.firstAttribute
relatedBy:cellConstraint.relation
toItem:seccondItem
attribute:cellConstraint.secondAttribute
multiplier:cellConstraint.multiplier
constant:cellConstraint.constant];
[self.contentView addConstraint:contentViewConstraint];
}
}
Here is a subclass, based on other answers ideas, I'm going to base my custom cells on:
#interface FixedTableViewCell ()
- (void)initFixedTableViewCell;
#end
#interface FixedTableViewCell : UITableViewCell
#end
#implementation FixedTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (nil != (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) {
[self initFixedTableViewCell];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self initFixedTableViewCell];
}
- (void)initFixedTableViewCell {
for (NSInteger i = self.constraints.count - 1; i >= 0; i--) {
NSLayoutConstraint *constraint = [self.constraints objectAtIndex:i];
id firstItem = constraint.firstItem;
id secondItem = constraint.secondItem;
BOOL shouldMoveToContentView = YES;
if ([firstItem isDescendantOfView:self.contentView]) {
if (NO == [secondItem isDescendantOfView:self.contentView]) {
secondItem = self.contentView;
}
}
else if ([secondItem isDescendantOfView:self.contentView]) {
if (NO == [firstItem isDescendantOfView:self.contentView]) {
firstItem = self.contentView;
}
}
else {
shouldMoveToContentView = NO;
}
if (shouldMoveToContentView) {
[self removeConstraint:constraint];
NSLayoutConstraint *contentViewConstraint = [NSLayoutConstraint constraintWithItem:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant];
[self.contentView addConstraint:contentViewConstraint];
}
}
}
#end
An alternative to subclassing is to revise the constraints in cellForRowAtIndexPath.
Embed all the content of the cell inside a container view. Then point the leading and trailing constraints to the cell.contentView rather than the table view cell.
UIView *containerView = [cell viewWithTag:999];
UIView *contentView = [cell contentView];
//remove existing leading and trailing constraints
for(NSLayoutConstraint *c in [cell constraints]){
if(c.firstItem==containerView && (c.firstAttribute==NSLayoutAttributeLeading || c.firstAttribute==NSLayoutAttributeTrailing)){
[cell removeConstraint:c];
}
}
NSLayoutConstraint *trailing = [NSLayoutConstraint
constraintWithItem:containerView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:contentView
attribute:NSLayoutAttributeTrailing
multiplier:1
constant:0];
NSLayoutConstraint *leading = [NSLayoutConstraint
constraintWithItem:containerView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:contentView
attribute:NSLayoutAttributeLeading
multiplier:1
constant:0];
[cell addConstraint:trailing];
[cell addConstraint:leading];
I think this is fixed in iOS 7 beta 3 making the workarounds unnecessary from that point on (but probably harmless as in most cases they will become empty operations).
Based on the code by Skoota (I am a beginner, don't know much of what you did, but excellent work) my suggestion is to put all your stuff in an edge-to-edge container view and add the following:
In the cell's header file, I have the following IBOutlets:
#property (weak, nonatomic) IBOutlet UIView *container;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *leftConstrain;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *rightConstrain;
In the implementation file, I have the following in awakeFromNib:
// Remove the IB added horizontal constraint, as that's effective gainst the cell not the contentView
[self removeConstraint:self.leftConstrain];
[self removeConstraint:self.rightConstrain];
// Create a dictionary to represent the view being positioned
NSDictionary *containerViewDictionary = NSDictionaryOfVariableBindings(_container);
// Create the new left constraint (0 spacing because of the edge-to-edge view 'container')
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-0-[_container]" options:0 metrics:nil views:containerViewDictionary];
// Add the left constraint against the contentView
[self.contentView addConstraints:constraints];
// Create the new constraint right (will fix the 'Delete' button as well)
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"[_container]-0-|" options:0 metrics:nil views:containerViewDictionary];
// Add the right constraint against the contentView
[self.contentView addConstraints:constraints];
Again, the above was made possible by Skoota. Thanks!!! Al credits go to him.