Where should I put AutoLayout code? - ios

I'm using PureLayout to implement AutoLayout of subviews in a UIView. But I don't know the best practice of organizing the code.
Should I put the AutoLayout related code in the init of the UIView, or the overridden methods such as updateConstraints and layoutSubviews?

For example, I want to create a subclass of UIView called PHView, and for any phview, there is a subview called centerView, it is always at the center of phview, and width/height is 0.3*phview's width/height.
https://www.dropbox.com/s/jaljggnymxliu1e/IMG_3178.jpg
#import "PHView.h"
#import "Masonry.h"
#interface PHView()
#property (nonatomic, assign) BOOL didUpdateConstraints;
#property (nonatomic, strong) UIView *centerView;
#end
#implementation PHView
- (instancetype)init {
self = [super init];
if (self) {
self.backgroundColor = [UIColor redColor];
self.translatesAutoresizingMaskIntoConstraints = NO;
}
return self;
}
- (UIView *)centerView {
if (!_centerView) {
_centerView = [UIView new];
_centerView.backgroundColor = [UIColor yellowColor];
[self addSubview:_centerView];
}
return _centerView;
}
-(void)updateConstraints {
if (!_didUpdateConstraints) {
_didUpdateConstraints = YES;
[self.centerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.mas_centerX);
make.centerY.equalTo(self.mas_centerY);
make.width.equalTo(self.mas_width).multipliedBy(0.3);
make.height.equalTo(self.mas_height).multipliedBy(0.3);
}];
}
[super updateConstraints];
}
#end
'didUpdateConstraints' aims to indicate you have added constraints, so you will only add constraints once.
in UIViewController:make phview top bottom left right 20 to the margin.
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor];
PHView *myView = [PHView new];
[self.view addSubview:myView];
[myView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(20, 20, 20, 20));
}];
}

You should add constraints when you are sure that view has been added to its superview. Basically, you should do it in superview's class any point after addSubview: is called.
To answer your questions:
1- in init methods, can you be sure of that view has been added as subview to a superview? it wouldn't be safe to assume that. maybe you can add constraints in init method of superview
2- layoutSubviews is in where autolayout code actually works. you can't add constraints in layoutSubviews. already playing with autolayout constraints are not cheap, therefore you should add/remove them as few as possible, doing so in a method that is called multiple times (i.e. layoutSubviews) is not the best practice.
Mechanism of autolayout is going to inner view from outer view, so subviews do not actually concern about constraints. it is superview's responsibility

Hope this helps you by understanding controller’s view hierarchy
How View Controllers Participate in the View Layout Process
The view controller’s view is resized to the new size.
If autolayout is not in use, the views are resized according to their autoresizing masks.
The view controller’s viewWillLayoutSubviews method is called.
The view’s layoutSubviews method is called. If autolayout is used to configure the view hierarchy, it updates the layout constraints by executing the following steps:
a.The view controller’s updateViewConstraints method is called.
b.The UIViewController class’s implementation of the updateViewConstraints method calls the view’s updateConstraints method.
c. After the layout constraints are updated, a new layout is calculated and the views are repositioned.
The view controller’s viewDidLayoutSubviews method is called.
Please refer this for more details

Related

Layout not changing when parent (UIView) size is changed

I have a UIView and inside this UIView I have another UIView, lets say parent and child UIView. I have set height and width of parent UIView to 400 in storyboard and set child view constraint to take 8px margin from top, left right and bottom from its superview.
But When I change size of parent view to 200, size of child view remain same. I have tried this in both viewdidload and viewdidappear
CGRect frm = self.mainTimerView.frame;
frm.size.width = size;
frm.size.height = size;
self.mainTimerView.frame = frm;
when I change parent view to 200 child should set it self to 200-16 height and width according to constraints.
You should not mix using uiconstraint with using frame.If you want to change the size when using uiconstraint, you should make the outlet of the constraint, and then change the constraint's constant property.Call layoutIfNeeded,then you can get the right frame.
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UIButton *mainTimerView;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *mainTimerViewHeightConstraint;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view layoutIfNeeded];
NSLog(#"%#",#(self.mainTimerView.frame.size.width));
self.mainTimerViewHeightConstraint.constant = 100;
[self.view layoutIfNeeded];
NSLog(#"%#",#(self.mainTimerView.frame.size.height));
}
#end
Calling layoutIfNeeded aim to to force the layout of subviews before drawing, then viewDidLayoutSubviews will be called. Note that you can get the correct frame in viewDidLayoutSubviews. In other words, you can get the right frame after viewDidLayoutSubviews has been called.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLog(#"%#",NSStringFromCGRect(self.mainTimerView.frame));
}
1) Change the height constraint of the mainview.
2) Call the layoutIfNeeded method.
3) Do it on viewDidAppear.
Have you tried calling setNeedsLayout on the parent view? This call is used to ask a view to layout its subviews. From the setNeedsLayout's discussion section:
Call this method on your application’s main thread when you want to adjust the layout of a view’s subviews. This method makes a note of the request and returns immediately. Because this method does not force an immediate update, but instead waits for the next update cycle, you can use it to invalidate the layout of multiple views before any of those views are updated. This behavior allows you to consolidate all of your layout updates to one update cycle, which is usually better for performance.
Calling layoutIfNeeded might be another option. But according to the docs, it forces an immediate layout, and therefore does not provide the consolidation benefit gained by using setNeedsLayout.
layoutIfNeeded documention:
Lays out the subviews immediately.
Plz create the IBOutlet of height Constraint of parent view.
IBOutlet NSLayoutConstraint *RedViewHeightconstraint;
- (void)viewWillAppear:(BOOL)animated
{
RedViewHeightconstraint.constant = 500;
}
it will work for you
You just need to create IBOutlets of constraints of height and width of mainTimerView. Once you do so add these lines of code :
constraintOuterViewHt.constant = 200
constraintOuterViewWidth.constant = 200
You don't need to do anything else.
Check screen shot here

Change NSLayoutConstraint within UIView Subclass

I am trying to affect the NSLayoutConstraint within my UIView subclass. However where ever I put the code it doesn't seem to change the autolayout constraint. I have used NSLayoutConstraint many times before but for some reason cannot seem to reference it in the subclass.
My constraint is
self.ripHeight.constant
Connected by
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *ripHeight;
I have tried it in the init and awakeFromNib methods as below
- (id)init {
self = [super init];
if (self) {
//self.ripHeight.constant = 100;
}
return self;
}
-(void)awakeFromNib {
[super awakeFromNib];
// 2 . Change Height of Ripll Container to suit device - bitch
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
//Create Nib Frame
CGRect frameRect = self.frame;
frameRect.size.width = screenWidth;
frameRect.size.height = screenWidth * 1.3333;
self.frame = frameRect;
self.ripHeight.constant = 100;
}
I am loading the view in by
[self.ripContainer addSubview:customView];
Try to update layout manually once you have changed the constraint :
-(void)awakeFromNib {
[super awakeFromNib];
//...
//your code for constraint
[self.superview setNeedsLayout];
}
- setNeedsLayout
Invalidates the current layout of the receiver and triggers a layout
update during the next update cycle.
Discussion
Call this method on your application’s main thread when you
want to adjust the layout of a view’s subviews. This method makes a
note of the request and returns immediately. Because this method does
not force an immediate update, but instead waits for the next update
cycle, you can use it to invalidate the layout of multiple views
before any of those views are updated. This behavior allows you to
consolidate all of your layout updates to one update cycle, which is
usually better for performance.
Possible approach to resolve this type of issue is using awakeAfterUsingCoder. It works as a placeholder view. Load your view here instead of in ViewController. Also set layout constraints right in the XIB.
For more information see this link.

Delegate when constraints updated

I am facing an issue while using storyboard auto layout with UIScrollView. I am updating a constraint of a UIScrollView, and after that setting the content size of scrollview. But UIScrollView constraint takes a bit of time to update and initially UIScrollView is not able to scroll because of more height than a view. I can not change the current implementation.
Is there any way, notification or any delegate method to check that the constraints are updated, so that I can do further changes?
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *scrollViewHieghtConstraint;
self.scrollViewHieghtConstraint.constant = 500;
[self.scrollView updateConstraints];
[self.scrollView setContentSize:CGSizeMake(0, 1000)];
You can use - (void)updateViewConstraints selector of UIViewController.
You can also use - (void)updateConstraints selector of UIView if you have extended said view.
You can't call -updateConstraints directly. You have to mark the view's constraints as dirty and iOS will call that method on the next update cycle.
On a UIViewController's implementation, add this when you need the constraint to change:
self.scrollViewHieghtConstraint.constant = 500;
[self.view setNeedsLayout];
I'm assuming that UIScrollView is a subview of the UIViewController's view. If it's not, then call -setNeedsLayout on the scrollview's parent view.
And if you need to know when the subviews are already on their right place, you have a callback for UIViewController, called -viewDidLayoutSubviews

Resize Parent view dynamically when Child view is added out of bound

UIView *parentView = [[UIView alloc]initWithFrame:[CGRectMake(0,0,100,100)]];
UIView *childView = [[UIView alloc]initWithFrame:[CGRectMake(0,120,100,100)]];
[parentView addSubview:childView];
The code above stating childView is added into parentView at a location outside of parentView bound. Is there anyway i can resize parentView so that the childView is no longer out of bound? I want to do this dynamically. Is there any auto function available, or I have to do it manually by calculating the overall size.
Another question is, before the resizing of parentView to fit in childView, it seems that the childView is added at (0,100) instead of (0,120) of parentView, even though I set [parentView setClipsToBounds:NO];
you can resize the parent view according to the child view frame
UIView *parentView = [[UIView alloc]initWithFrame:[CGRectMake(0,0,100,100)]];
UIView *childView = [[UIView alloc]initWithFrame:[CGRectMake(0,0,0,120)]];
[parentView addSubview:childView];
parentView.frame = CGRectMake(0,0,childView.frame.size.width,childView.frame.size.height);
If AutoLayout is an option for you I would recommend looking into intrinsic content size. You can set the constraints so that the parent view will resize according to the content (childView) in Interface Builder. There is a very good tutorial on AutoLayout on Ray Wenderlich
You can create a custom UIView and override the addSubView method, in your addSubView override implementation you can check the size of the subview being added if needed and resize the current view.
#implementation CustomView
- (void)addSubview:(UIView *)view {
if(!CGSizeEqualToSize(self.view.size, view.size)) { // Sizes don't match
// resize self.view here
}
}
#end

Update the preferredMaxLayoutWidth for a multiline UILabel with unknown size (Auto Layout)

I have a custom view class which inherits from UIView. This class has an UILabel as its subview. In the init-function of this custom view class I set up everything needed like this:
//h-file
#import <UIKit/UIKit.h>
#interface MyCustomView : UIView
#property (strong, nonatomic) UILabel *myLabel;
#end
//m-file
#implementation MyCustomView
#synthesize myLabel = _myLabel;
- (id)init
{
self = [super init];
if (self) {
_myLabel = [UILabel new];
if(_textView){
_myLabel.highlightedTextColor = [UIColor whiteColor];
_myLabel.translatesAutoresizingMaskIntoConstraints = NO;
_myLabel.lineBreakMode = NSLineBreakByWordWrapping;
_myLabel.numberOfLines = 0;
_myLabel.backgroundColor = [UIColor clearColor];
[self addSubview:_myLabel];
}
}
return self;
}
#end
I also set up a bunch of constraints to manage padding inside my custom view - furthermore there are constraints to layout multiple MyCustomView-instances for both vertical and horizontal axis as well.
To get a multilined label output I have to set the preferredMaxLayoutWidth-property of the UILabel myLabel. The width depends on the free space available. At http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html I read, that I can let Auto Layout calculate the width first and set it as preferredMaxLayoutWidth after the frame of the MyCustomView-instance (the label inside is single lined at this moment) has been set.
If I put the following function into the MyCustomView, the label still has a single line of text:
- (void)layoutSubviews
{
[super layoutSubviews];
float width = _myLabel.frame.size.width;
_myLabel.preferredMaxLayoutWidth = width;
[super layoutSubviews];
}
If I set the preferredMaxLayoutWidth to an explicit value inside the init-function, the label is multilined.
Does anybody know what I am doing wrong here?
Thanks in advance!
Without seeing all the constrains you have setup for your custom view, and the superview that contains it, it's really hard to determine the problem, I suggest you to print out all the view frames of the entire view hierarchy starting from the view controller's view at viewDidLayoutSubviews and determine if the label and its superviews have correct frame set.
I have an encountered similar issues with dynamic label size and scroll view so I created a prototype here, might be useful to you too: https://github.com/briandotnet/AutoLayoutScrollViewExperiment

Resources