Delegate when constraints updated - ios

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

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

Where should I put AutoLayout code?

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

How can I get the frame of a UIView subclass using Autolayout with Storyboard?

When I use a subclass of UIView and put it on the storyboard with some constraints,i check its frame in awakeFromNib() or didMoveToWindow(). The result is not correct cause the constraints are not applied yet.
Is there any callback to inform me that the constraints are applied? So after that i can do sth with the correct frame.
When awakeFromNib viewDidLoad or didMoveToWindow is called, layout is not completed yet. You have to use viewDidLayoutSubviewsof a viewController. This method will be called after applying constraints and completing layout.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
}

UIScrollView not scrolling after changing from regular View

I have a xib with a View, I changed the View class to be UIScrollView.
Now I have set contentSize to some big number (2000, 2000).
Here is my code:
This allows talking to my scrollview without casting every time:
- (UIScrollView *)scrollView {
return (UIScrollView *)self.view;
}
This I do in viewDidLoad:
self.scrollView.contentSize = CGSizeMake(self.view.frame.size.width, 2600);
self.scrollView.scrollEnabled = YES;
I know viewDidLoad is getting called fine. But the scrollView is not reacting. I don't have any touch responders or anything that can absorb touches.
For this, first set delegate of scrollView inside your xib files then create an IBIoutlet for your scrollview. Remind that scrollView should connect with its refrencing outlet then Assign a propertyy as Strong to your scrollView.
#property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
and synthesize it.
#synthesize scrollView;
then try to set contentSize.
scrollView.delegate=self;
scrollView.contentSize = CGSizeMake(self.view.frame.size.width, 2600);
scrollView.scrollEnabled = YES;
I hope it works for you.
I have found a problem, I was setting superview too small.

size of UIView object returned as zero under autolayout

I'm re-writting an app under IOS6 with autolayout but have problems accessing the size of a subclass of UIView
#property (weak, nonatomic) IBOutlet MyView *myView1;
in
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"myView1: %#",self.myView1);
}
The size of the UIView is fully defined in the StoryBoard but comes out as zero when I run the code. Is this todo with auto layout or is the subview simply not defined yet? Am I doing something basically wrong here?
Just found one possible answer here. Running the methods
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
forces a calculation of UIViews dimensions causing the expected values to appear in the log and also be known unto the UIView subclass itself.
I just printed the frame of a subview.
It was 0 in viewDidLoad.
It had a non-zero value inside the viewDidLayoutSubviews callback.
Inside the viewDidLoad you should only expect that the value of the viewcontroller's view to be non-zero. Inside viewDidLoad is actually where sometimes you add subviews and stuff. So it's not correct to expect the frame to get calculated in the very callback that you're settings constraints for it.
Basically what needs to be understood is that first a viewController is drawn, then as it goes through its life cycle events, its subviews will get laid out

Resources