i'm having a problem with embedded views and auto layout.
I've created a view, which is a little complex. So now
I want to refactoring this view and create some view components. I got one of the views and take together in one uiview class, and put all its logic there. Lets call this view as XView. All right until now.
So I tried to embed XView in the main view, to see the view works, with its new component. I put this commands:
xViewInstance = ...
[self.container addSubview:xViewInstance];
It doesn't work. the xViewInstance is bigger than the parent view. I want to resize xViewInstance.
So I googled for answers to see what's going wrong. And I found some answers that could helped me. I found PureLayout.
So I tried with it.
- (void)updateViewConstraints {
if (!self.didSetupConstraints) {
[self.xViewInstance autoPinEdgesToSuperviewEdges];
self.didSetupConstraints = true;
}
[super updateViewConstraints];
}
It didn't work. xViewInstance continues bigger than its parent.
I found another answer here in stack, a code that create constraints in code, to adjusts subviews programmatically. Again it didn't work.
Now I have no ideia whats could be. I'm thinking that could some priority of the xViewInstance constraints.
Have someone ever passed for this situation? I would be very grateful if anyone can give some advice about this.
I believe this post will solve your problem:
Use autolayout to set dynamic UIView to match container view
I tested it like this and it worked:
- (void)viewDidLoad {
[super viewDidLoad];
// Init with a huge frame to see if it resizes.
xView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1024, 800)];
xView.translatesAutoresizingMaskIntoConstraints = NO;
xView.backgroundColor = [UIColor redColor];
[containerView addSubview:xView];
[self addConstraints];
}
- (void)addConstraints
{
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:xView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0]];
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:xView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0.0]];
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:xView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0]];
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:xView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:0.0]];
}
Wg
Related
I'm developing a custom keyboard and i want to use only a UICollectionView as content. The CollectionView should fill (center x and y, width and height available space) of the keyboardview.
My problem is that if i rotate the device the collection view won't be resized.
I tried layout constraints, resizing the view on viewWillTransitionToSize but it does not work. Can anyone give me a hint or show me a way to get this done?
Autolayout should set translatesAutoresizingMaskIntoConstraints to NO before adding Constraint. The code below can make a UICollectionView autolayout with equal edges.
- (void)viewDidLoad {
[super viewDidLoad];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
collectionView.translatesAutoresizingMaskIntoConstraints = NO;
collectionView.backgroundColor = [UIColor redColor];
[self.view addSubview:collectionView];
//Adding layout constraint
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:collectionView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:collectionView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:collectionView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:collectionView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0.0]];
}
In addition, I recommend you to use Masonry to build autolayout. In your case, you just need to run the code below to make constraints.
[collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
By the way, autolayout will use more memory then calculating frame yourself. As iOS Keyboard extension has a very harsh memory usage limit (around 40mb depends on your device). If your keyboard would use many memory on process other things, I suggest you don't use autolayout.
I am trying to add constraints to UIScrollView with a label (subview of scrollview)
but the scrollview wouldn't scroll and trailing never work.
float y = self.navigationController.navigationBar.frame.size.height + 30;
self.scrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0,0, 0,0)];
[self.scrollView setBackgroundColor:[UIColor blueColor]];
[self.scrollView setScrollEnabled:YES];
[transparentImageView addSubview:self.scrollView];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[transparentImageView addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:transparentImageView attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-120.0]];
//leading
[transparentImageView addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:transparentImageView attribute:NSLayoutAttributeLeading multiplier:1.0f constant:20.0f]];
//trailing
[transparentImageView addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:transparentImageView attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:-20.0]];
[transparentImageView addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:transparentImageView attribute:NSLayoutAttributeTop multiplier:1.0f constant:y]];
self.shineText = [[RQShineLabel alloc]initWithFrame:CGRectMake(0,0, 0, 0)];
[self setupText];
[self.shineText setBackgroundColor:[UIColor redColor]];
[self.scrollView addSubview:self.shineText];
self.shineText.translatesAutoresizingMaskIntoConstraints = NO;
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width, self.scrollView.frame.size.height);
//bottom
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.shineText attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-10.0f]];
//leading
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.shineText attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeLeading multiplier:1.0f constant:10.0f]];
//trailing
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.shineText attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:-500.0f]];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.shineText attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeTop multiplier:1.0f constant:20]];
If you have this line self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width, self.scrollView.frame.size.height); then your scroll view never make scroll at all because your content size is the same as the scroller frame, the content size of your scrollView must be greater than the scrollView frame in order to scroll
Try with something like this
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width, 1000);
then your scrollView must scroll
I hope this helps you
Generally, while working with UIScrollView and auto layouts, we need to add one UIView as child view on scroll view with same size as scroll view.
Here is tutorial which explains this in details.
But this tutorial is for using scroll view in .nib file.
For adding scroll view programatically, go through Apple developer technical note.
It suggest that:
Use constraints to lay out the subviews within the scroll view, being sure that the constraints tie to all four edges of the scroll view and do not rely on the scroll view to get their size.
try this tricky way to work with scrollview in storyboard. I created a small video tutorial to show how easy to work with scroll view. You should have a small knowledge of autolayout.
step 1 : change your viewcontroller size as your need
step 2 : add a scroll view and setup constarints (top, lead, trail and top) to the conatainer view.
step 3 : then add another view on to scroll view(this is your child view where you add your all other IBOutlets)
step 4 : then add constraints to it. (top, lead, trail and top to scroll view) + (width and height to it self)
step 5 : create an outlet to the child view width to your controller and give the width programatically( the device width)
this small tutorial shows you how to do it..
part one : less thant 5 min
part two : less than 5 min
hope this will help to you.
Is it possible to change autolayout constraints during runtime?
I know you can change the constant, but how would you change different attributes.
For example, NSLayoutAttributeTop to NSLayoutAttributeBottom?
Here is a simple sample of what I hope to achieve, it will set a label top left, then when you press a button it will set the label bottom right.
The initial constraints work as expected, tapping the button doesn't work as expected and throws the infamous "Unable to simultaneously satisfy constraints."
Here is the code I am using:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
self.constraintA = [NSLayoutConstraint constraintWithItem:self.topLabel
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0];
self.constraintB = [NSLayoutConstraint constraintWithItem:self.topLabel
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0.0];
[self.view addConstraint:self.constraintA];
[self.view addConstraint:self.constraintB];
}
- (IBAction)tappedChange:(id)sender
{
[self.view removeConstraints:#[ self.constraintA, self.constraintB ]];
self.constraintA = [NSLayoutConstraint constraintWithItem:self.topLabel
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0];
self.constraintB = [NSLayoutConstraint constraintWithItem:self.topLabel
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0.0];
[self.view addConstraint:self.constraintA];
[self.view addConstraint:self.constraintB];
[self.view setNeedsLayout];
}
Thank you for your time.
You only need to perform the Remove/Recreate/Add constraint dance on iOS 7 and below. If you are writing for a modern iOS (8 and above) you can create all your constraints at once and then just set the .active property on whatever NSLayoutConstraint instance you want at any given time.
// Move to right
self.leadingConstraint.active = false;
self.trailingConstraint.active = true;
// Move to bottom
self.topConstraint.active = false;
self.bottomConstraint.active = true;
If you are using Interface Builder you can create all the constraints that will be needed (note the grayed out constraints that aren't active by default).
Then when the button is pressed you can deactivate the old constraints and activate the new ones.
If you are ever unsure about the views being shown you can pause the app execution and use the following private API in the debugger to print out a full list of views and constraints:
po [[UIWindow keyWindow] _autolayoutTrace]
Personally I like using Masonry to manage constraints in code - it's way less writing, easier to read what you wrote six months ago, and the learning curve isn't as head-splitting either.
For example:
// Define some constraints, making the top one a #property:
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
self.topConstraint = make.top.equalTo(superview.mas_top).with.offset(padding.top);
make.left.equalTo(superview.mas_left).with.offset(padding.left);
}];
// Update the top constraint to match the bottom of the superview instead of the top
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
self.topConstraint = make.top.equalTo(superview.mas_bottom).with.offset(padding.bottom);
}];
I have problem with setting constraints to custom UIView and pushing new ViewController by delegate when I tap the view. I tried to find solution for these problems but couldn't find one that answers to these questions.
Autolayout problem
I have custom xib-file which has been set Size: Freeform, Width: 600 and Height: 25, it also includes one label and one button with constraints in this view. I have added this view successfully below navigation bar where I want it. Problem is, that it don't make anything to fit it's width equally with navigation bar / window size (I have tried multiple choices eg. making new frame for view that is width of window / navigation bar). It only appears to have static 600 width all the time whatever I try.
First two constraints are working, it appears 25 points below navigation bar and it centers it. But last one won't make anything.
How should I do this properly? So far have this:
[self.subView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:self.subView];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.subView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.navBar
attribute:NSLayoutAttributeBottom
multiplier:1
constant:25.0]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.subView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.subView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:1
constant:0]];
Should I do something more with xib-file that it will make this width to fit it's parent view? I have also implemented initWithFrame, initWithCoder and intrinsicContentSize to my custom view.
Solution
I ended up to make containerView for my subView and center it vertically and horizontally and found right constraint for width. I also forgot to update my subView's view frames to match navigation bar width. Here is what I ended up to (if there is better way to do this, I take critic with pleasure):
self.containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 62, self.navBar.frame.size.width, 25)];
[self.view addSubview:self.containerView];
self.subView = [[SubView alloc]init];
[self.subView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.containerView addSubview:self.subView];
self.subView.view.frame = CGRectMake(0, 0, self.containerView.frame.size.width, self.containerView.frame.size.height);
[self.containerView addConstraint:
[NSLayoutConstraint constraintWithItem:self.subView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.containerView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
[self.containerView addConstraint:
[NSLayoutConstraint constraintWithItem:self.subView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.containerView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[self.containerView addConstraint:
[NSLayoutConstraint constraintWithItem:self.subView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.containerView
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0]];
Delegate problem (solved)
For answer to this problem: check MisterGreen's answer below.
Another problem occured when I made UITapGestureRecognizer with delegate in my custom view. What I want is when I tap the view, it opens another ViewController. The delegate function is like this where I implement my custom view:
-(void)pushViewControllerUsingDelegate
{
NSLog(#"DELEGATE WAS : %#", self.subView.delegate);
[self pushViewController:self.anotherViewController animated:YES];
}
Now it gives exception when I tap the view:
DELEGATE WAS : <MasterViewController: 0x7fc96132e7d0> <-- Delegate is OK
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<AnotherViewController 0x7fc961248230> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key subViewButton.'
What this actually means? I have this subViewButton IBOutlet with weak property, does it have something to do with this? Or is there another way to make this happen?
Tutorial which I followed: https://www.youtube.com/watch?v=TfKv1MYxnA4
Because there is not enough data to be exactly sure what is the problem you encountered, i have just created a code snippet that is working and doing exactly what you are trying to get.
About the constraints i think the problem is the hight constraint that is missing(unless you determined it elsewhere),
try to remember that when you add constraints provide enough data to the compiler to understand how to resize and position your subview according to it's superview, in your case it didn't know what is the hight cause you didn't supply nor bottom or hight constraint to determine it.
About the delegate method you didn't supply enough data to exactly determine what is the problem, so i've written something that i think is doing what you are trying to get.
This code snippet is tested and working:
The subview:
View.h
#protocol viewManager <NSObject>
#optional
- (void)subviewWasTapped;
#end
#interface View : UIView
#property (nonatomic, strong) id<viewManager>delegate;
#end
View.m
#implementation View
- (void)awakeFromNib{
[super awakeFromNib];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(viewWasTapped:)];
[self addGestureRecognizer:tap];
}
- (void)viewWasTapped:(NSNotification *)notification
{
[self sendViewWasTappedToDelegate];
}
- (void)sendViewWasTappedToDelegate
{
#synchronized(_delegate)
{
if([_delegate respondsToSelector:#selector(subviewWasTapped)])
{
[_delegate subviewWasTapped];
}
}
}
#end
FirstViewController:
#interface ViewController () <viewManager>
#property (nonatomic, strong) View *subview;
#end
#implementation ViewController
#synthesize subview;
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"View" owner:self options:nil];
subview = [subviewArray objectAtIndex:0];
[subview setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:subview];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.topLayoutGuide
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:1
constant:0.0]];
// Height constraint to determine the
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:25.0]];
[self.view layoutIfNeeded];
[subview setDelegate:self];
}
#pragma mark - viewManager delegate method
- (void)subviewWasTapped{
SecondeViewController *secondeVC = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondeViewController"];
[self.navigationController pushViewController:secondeVC animated:YES];
}
Simply adding a subview UIView from a controller creating using instantiateViewControllerWithIdentifier to the current controllers view and adding constraints to maintain the size of the new subview to cover the entire area. The code below worked in iOS 7. iOS 8 sizes the subview to a small portion of the upper left corner. iOS 7 does what I expect, which is to size the subview across the entire size of the parent view. I'd attach an image, but don't have the rep for that. Setting translatesAutoresizingMaskIntoConstraints to YES fixes the issue, but then the view does not honor the constraints and resize when orientation changes or sizing changes.
spotCheckStoresViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"visitStoresViewController"];
spotCheckStoresViewController.mainViewController = self;
spotCheckStoresViewController.dataMode = kDataModeViews;
spotCheckStoresViewController.lastRow = [self getViewsLastRow];
spotCheckStoresViewController.view.tag = -200;
currentView = spotCheckStoresViewController.view;
[self addChildViewController:spotCheckStoresViewController];
[self.view insertSubview:currentView belowSubview:_menuViewController.view];
currentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:currentView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:currentView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:currentView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:currentView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0]];
//[self.view updateConstraints];
//[self.view layoutIfNeeded];
I've tried setting the frame of the subview as well. Any ideas what might have changed in iOS 8 to alter the behavior of constraints used in this way?
In my case, the problem related to constraints labeled UIView-Encapsulated-Layout-Width and UIView-Encapsulated-Layout-Height. When I removed them, everything behaved as though my view was of zero size, with everything centered on the upper left corner of the screen. When I left them in, new constraints worked as expected. I also retained the constraints labeled _UILayoutSupportConstraint.
In my case, it was only happening on iOS 8.3. There was a conflict with existing constraints. A colleague found that I needed to first remove any existing constraints before adding the others.
[self.view removeConstraints:self.constraints];