Where to add programmatic constraints in UIView subclass? - ios

I have a custom UIView subclass called CustomRectangle. I instantiate this in a ViewController and create all of its constraints in a ViewController. My goal is to create all the constraints inside this UIView subclass programmatically. The problem is that I don't know how to set the constraints there because I don't have reference to any of the other views in the Storyboard.
For example, if I want my view CustomRectangle to be centered based on another view, I would create an #IBOutlet in the ViewController for the other view and then use that to center CustomRectangle. I don't know if this is possible to do in a UIView subclass.
I want to do this based on MVC (Model View Controller) architecture.
What is the best practice for this? Any ideas on how to do this?

you should use initWithFrame method of CustomRectangle to do this.
for emample in UIView's Subclass,
- (id)initWithFrame:(CGRect)frame{
self = [super initWithFrame: frame];
if (self) {
[self setFrame:CGRectMake(0, 0, 50, 50)];
//add constraint here for example
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute: NSLayoutAttributeNotAnAttribute multiplier:1 constant:50]];
}
return self;
}
Convert in swift this is objective c code!!
Hope this will help :)

There are a few things you should do:
Remember to set translatesAutoresizingMaskIntoConstraints = NO for the constraints to work. However, if you think views autolayout way, inside your view you should use autolayout for your subviews, and the view that instantiates your view should add constraints to customRectangle (a.k.a widht, alignment, etc)
In the init method, you can just add constraints that only depend on your view. Other constraints should be added from outside.
For Example:
CustomRectangle *customRectangle = [[CustomRectangle *customRectangle alloc] init];
// just to be sure
customRectangle.translatesAutoresizingMaskIntoConstraints = NO;
// just as an example
[self.view addConstraints[NSLayoutConstraint constraintWithItem:customRectangle attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]];
Hope it helps!!

Related

Embed a UIView in another UIView with cascading Autolayout

My goal:
I'm trying to find a general recipe how to add views that I created in Interface Builder to a parent view that I create in Interface Builder as well, while letting their layout constraints determine the size of their respective parent views.
The key idea is to get cascading views with a variable size. A view should first determine its own size by resolving its layout constraints. Once it's done it has a fixed size from its superview's perspective so now the superview can determine its size by resolving its own layout constraints and so on.
My approach:
I create a new XIB file in Xcode and open it in Interface Builder. Next, I drag another UIView from the Object library to the XIB's view in order to add it as a subview. Using layout constraints I pin all four sides of the subview to its superview's edges:
Now I create another XIB file which is supposed to be the subview. I drag a UILabel and a UIButton to its view. I give the button a fixed height constraint and add more constraints to pin the label and the button to the view's edges:
Now comes the tricky part that I haven't been able to solve yet:
I couldn't find any way to add the view of this second XIB file to the subView of the first XIB using only Interface Builder (is this even possible?) so I tried it by writing some code. I created a UIView custom class and set the class value in Interface Builder accordingly. In the custom class I added this method:
- (void)awakeFromNib {
UIView *view = [[[NSBundle mainBundle] loadNibNamed:#"DynamicSubView" owner:self options:nil] objectAtIndex:0];
[self.subView addSubview:view];
view.translatesAutoresizingMaskIntoConstraints = NO;
[self addConstraintsForView:view];
}
- (void)addConstraintsForView:(UIView *)view {
[self addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.subView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.subView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.subView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.subView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]];
}
The method addLayoutConstraintsForView: adds constraints to the view from the second XIB that pins all its four edges to the subView's edges. Now the subView should resize dynamically with the view it contains. However, it doesn't seem to work. Any idea or a good recipe how to deal with this?
Remark: The top most view here is intended to be a UITableViewCell. I want it to layout itself and pass its height to its tableView using the method introduced here.

Positioning a view on top of a tableview using Auto Layout

I have a tableview in my storyboard that has its class set to my UITableView subclass which is named SPSExplanationTableView. There are no constraints set on this tableview in Interface Builder.
I am trying to programmatically create a UIView that displays in front of the tableview—which I know how to do (blog post link)—but that is sized and positioned using Auto Layout. This is my code:
#import "SPSExplanationTableView.h"
#interface SPSExplanationTableView()
#property (nonatomic, strong) UIView *explanationView;
#end
#implementation SPSExplanationTableView
- (void)awakeFromNib
{
[super awakeFromNib];
self.explanationView = [[UIView alloc] init];
self.explanationView.translatesAutoresizingMaskIntoConstraints = NO;
self.explanationView.backgroundColor = [UIColor blueColor];
[self addSubview:self.explanationView];
[self bringSubviewToFront:self.explanationView];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:self.explanationView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0f constant:150.0f];
[self.explanationView addConstraint:heightConstraint];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:self.explanationView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0f constant:200.0f];
[self.explanationView addConstraint:widthConstraint];
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self.explanationView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterY
multiplier:1.0f constant:0.0f];
[self addConstraint:topConstraint];
NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:self.explanationView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.0f constant:0.0f];
[self addConstraint:leftConstraint];
#end
When I run the app it crashes with the following assertion failure:
*** Assertion failure in -[SPSExplanationTableView layoutSublayersOfLayer:],
/SourceCache/UIKit_Sim/UIKit-2935.137/UIView.m:8794
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still
required after executing -layoutSubviews. SPSExplanationTableView's
implementation of -layoutSubviews needs to call super.'
Taking the message literally and overriding layoutSubviews has no effect i.e. I still get the same crash.
- (void)layoutSubviews
{
[super layoutSubviews];
}
What's the correct way to implement what I'm trying to achieve?
For Aubada Taljo, here's the tableview in Interface Builder:
Update: I solved this myself in the end by not using Auto Layout! I overrode the layoutSubviews method in my SPSExplanationTableView class and set the center property of the explanationView to the centre of self's bounds, with some slight adjustments to the y-axis position to make it look how I wanted it.
This is crashing because UITableViews are not designed to do this. A UITableView is only concerned about its cells, the headers, maybe its background, and it has logic for this that doesn't use autolayout. So it will crash if you try to involve it in any constraints calculation between it and any subviews, e.g. this will also crash if you add a constraint between the cell and the table.
What I suggest you do is to add a superview that will contain both your table and the view that you want to overlay:
SuperviewWithConstraints
|
|-- YourTableViewWithConstraintsRelativeToSuperview
|
|-- YourOverlayWithConstraintsRelativeToSuperview
And set up the constraints there. Then make that superview as your view controller's view. You will have to move away from using the UITableViewController as the controlling class, though.
If you would take my advice and make your life easier, simply go to your storyboard in Interface Builder and set the correct constraints on the your table view, now go back to the code editor and in the UIViewController that owns your table view, write the view creation code in viewDidLoad e.g.
UIView* someView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, self.tableView.frame.size.height)];
[self.view addSubView someView];
I guess this should be more than enough to solve your issue, now if you face any problems, move your code to viewDidLayoutSubViews in the UIViewController
Please tell me if you need more details but I use this way of creating dynamic controls all the time.

Why am I getting a "Auto Layout still required after executing -layoutSubviews" error every time my app launches now?

Since I added the following code, every time my app opens this UITableViewController it crashes:
self.noArticlesView = [[UIView alloc] init];
self.noArticlesView.translatesAutoresizingMaskIntoConstraints = NO;
self.noArticlesView.backgroundColor = [UIColor colorWithRed:0.961 green:0.961 blue:0.961 alpha:1];
[self.view addSubview:self.noArticlesView];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.noArticlesView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.noArticlesView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.noArticlesView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.noArticlesView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:0.0]];
And it gives me this error:
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super.'
What on earth am I doing wrong? I call that code in tableView:numberOfRowsInSection: when there's 0 rows.
I was subclassing UIScrollView and received the same error message on iOS 7 (but not 8).
I was overriding layoutSubviews in a manner similar to the following:
- (void)layoutSubviews {
[super layoutSubviews];
// code to scroll the view
}
I resolved the issue by moving the call to super's layoutSubviews to be the last thing in the method:
- (void)layoutSubviews {
// code to scroll the view
[super layoutSubviews];
}
Had the same problem. Added view(s) to self.tableView and used constraints. Do not add the views to the table view via addSubview: but add them as header(s), footer(s) or cells.
[self.view layoutIfNeeded]
Hope this helps
You also need to disable mask translation for the table view.
for me is was this
self.tableView.backgroundView = self.emptyView;
I changed to this
NSComparisonResult order = [[UIDevice currentDevice].systemVersion compare: #"8.0" options: NSNumericSearch];
if (order == NSOrderedSame || order == NSOrderedDescending) {
// OS version >= 8.0
self.tableView.backgroundView = self.emptyView;
}else{
[self.tableView.backgroundView addSubview:self.emptyView];
}
Checkout "Auto Layout still required after executing -layoutSubviews" with UITableViewCell subclass as the question appears to be the same.
I was able to implement the category mentioned in one of the answers which solved the problem for me. However, I had to create the category on the UITableView class instead of the UITableViewCell class as is discussed in that particular answer.
You can add your 'no articles view' as a custom header in the table to make sure it's positioned correctly.
A possible solution is not to add the noArticlesView directly in the table, but is to put the UITableView inside a container UIView (eventually setting table constraints to fit with the container frame) and then constraint your noArticlesView to the container, using the same constraints you set and in the same place in your code, that is inside the -tableView:numberOfRowsInSection: UITableViewDataSource method.
I tested it with a simple example, and it worked.
The changes you need to apply to your code are to replace the UITableViewController with a UIViewController, add a container view (unless you want your table to fit exactly with the view controller's view, in such case this view is the container) and then constraint your noArticleView to the container instead of the table.
My example code is at the bottom of this answer.
I will try to make a possible explanation of the reason of the issue and why this solution works, but consider that part of my explanation is based on guesses so it couldn't be completely exact.
First of all a brief explanation of how the view hierarchy rendering process works in iOS. The first step for the layout engine is to determine the size and position of all views and subviews in the view hierarchy. Usually this is done with an iterative approach, where auto layout constraints are evaluated, in order to determine size and position of views and subviews, and then each view's -layoutSubviews method is called for fine tuning: this means that you can change your layout after constraints are evaluated. What the layout engine requires is that if the -layoutSubviews method changes constraints again, then -[super layoutSubviews] must be called again to allow the iterative process: if this doesn't happen the layout engine raises an exception. In the case of the UITableView my guess is that the tableView:numberOfRowsInSection: method is called somewhere within the internal UITableView layoutSubviews method and this method doesn't call [super layoutSubviews]. So if your table data source method updates internal table constraints, at the end of the method the exception is triggered. The solution I propose works because the only constraint applied to the table is the external constraint towards the container, so evaluated at the first stage of the layout process, while the constraints added in the table data source method are not relevant to the table as they are applied to views external to the table (container and noArticlesView) so they don't affect the internal table view layout process.
//
// RootController.h
#import
#interface RootController : UIViewController
#property (nonatomic,strong) IBOutlet UITableView *table;
#property (nonatomic,strong) IBOutlet UIView *container;
#end
//
// RootController.m
#import "RootController.h"
#implementation RootController
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
UIView *_v = [[UIView alloc] init];
_v.backgroundColor=[UIColor redColor];
_v.translatesAutoresizingMaskIntoConstraints=NO;
[self.container addSubview:_v];
NSLayoutConstraint *_c1 = [NSLayoutConstraint constraintWithItem:_v
attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.container attribute:NSLayoutAttributeTop multiplier:1.0 constant:20.0];
NSLayoutConstraint *_c2 = [NSLayoutConstraint constraintWithItem:_v
attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.container attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-20.0];
NSLayoutConstraint *_c3 = [NSLayoutConstraint constraintWithItem:_v
attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.container attribute:NSLayoutAttributeLeft multiplier:1.0 constant:20.0];
NSLayoutConstraint *_c4 = [NSLayoutConstraint constraintWithItem:_v
attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.container attribute:NSLayoutAttributeRight multiplier:1.0 constant:-20.0];
[self.container addConstraints:#[_c1,_c2,_c3,_c4]];
return 0;
}
I unchecked auto layout on the view controller the crash is not occurring now

iOS Autolayout and UIToolbar/UIBarButtonItems

I have an iOS view with autolayout enabled and have a UIToolbar with a UISearchBar and UISegmentControl contained with the toolbar. I want the UISearchBar to have a flexible width so I need to add a constraint to force this, but from what I can tell you cannot add constraints to items in a UIToolbar in Interface Builder. The options are all disabled.
Before AutoLayout I would accomplish this with autoresizingmasks.
Are constraints not allowed within UIToolbars/UINavigationBars?
How else can this be accomplished when using autolayout?
Autolayout constraints only work with UIViews and their subclasses.
While UIToolbar allows some UIView based items (such as UISearchBar and UISegmentedControl) they may have to coexist with UIBarButtonItems which do not inherit from UIView.
Until autolayout can work with UIBarButtonItems, do as you have done.
Your alternative is to roll your own toolbar with widgets based only on UIViews.
This can also be done right from a storyboard.
Just drag and drop items in the toolbar, and turn some of them into flexible or fixed space to get the desired effect. See the two examples below.
NB: this is a copy of my answer to Aligning UIToolBar items, I stumbbled upon both questions while looking for such a solution
You can do this in code, at least; I'm the type to forsake Interface Builder and go it in code anyway. IB seems to get in my way more often than not when it comes to adding or tweaking constraints. Here's what I've done in my custom UIToolbar subclass's -initWithFrame: method.
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self addSubview:self.label];
[self addConstraint:[NSLayoutConstraint
constraintWithItem:self.label
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1 constant:0]];
[self addConstraint:[NSLayoutConstraint
constraintWithItem:self.label
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterY
multiplier:1 constant:0]];
}
return self;
}
And since I like to lazy load as much as possible, here's my self.label instance variable (called when [self addSubview:self.label] gets messaged above).
- (UILabel *)label {
if (_label) return _label;
_label = [UILabel new];
_label.translatesAutoresizingMaskIntoConstraints = NO;
_label.textAlignment = NSTextAlignmentCenter;
return _label;
}
Seems to work for me. I'm not adding any UIBarButtonItems, though, so your mileage my vary.

How to add constraints programmatically to a programmatically created UIView?

I have created a UIView using the following code within viewDidLoad (where 'secondview' obviously is the name of the UIView):
secondview = [[UIView alloc] initWithFrame:self.view.frame];
[secondview setBackgroundColor: [UIColor yellowColor]];
secondview.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:secondview];
Then within viewDidAppear I added constraints to this view:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:secondview attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0f constant:-20.0f];
[self.view addConstraint:constraint];
However, the constraints are not applied to the view (atleast not that I can see). Instead, the view simply seems to disappear from the screen. If the constraint code is commented out however, the view once again loads with the appropriate frame (obviously without the constraints being applied). When applying the same constraints to a Button or ImageView, the constraints are applied perfectly. This has lead me to think that the issue is because of 'initWithFrame' when creating the View, as neither the button nor ImageView actually require it's size to be specified.
What are your thoughts? What should I do differently?
For anyone who comes across this... I needed to add more than one constraint. That did the trick.

Resources