View not found in container hierarchy crash while adding View constraints programatically - ios

Although the crash message is very clear but I have checked my code and everything seems to be looking fine. My only worry is because I am setting constraints on a view which is being set as accessory view for keyboard, could that be a reason why entire view hierarchy is not set properly.
Here is what I am doing:
Step 1: Create a custom accessory view (a number bar from 0 to 9) and put it on top of regular keyboard.
MyAccessoryView *anAccessoryView = [[MyAccessoryView alloc] initWithFrame:CGRectMake(0, 0, 320, 46.0f)];
anAccessoryView.delegate = self;
self.textField.inputAccessoryView = anAccessoryView;
Step 2: In custom accessory view (subview of UIView), add buttons and related handling. Add them to super view and then call a method after a delay of 1 second to add constraints.
- (id)initWithFrame:(CGRect)iFrame {
if (self = [super initWithFrame:iFrame]) {
[self refresh];
self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
}
[self setExclusiveTouch:YES];
return self;
}
- (void)refresh {
if (!self.layoutController) {
self.layoutController = [[MyAccessoryViewLayoutController alloc] init];
}
self.backgroundColor = [self.layoutController colorForNumericInputAccessory:self];
self.userInteractionEnabled = YES;
NSMutableArray *buttonsArray = [NSMutableArray arrayWithCapacity:10];
// Adding number keys to the accessory view and setting thier properties
for (NSInteger i = 1; i < 11; i++) {
MyAccessoryViewNumericButton *aKeyboardButton = [MyAccessoryViewNumericButton buttonWithType:UIButtonTypeCustom];
aKeyboardButton.tag = i;
aKeyboardButton.frame = CGRectMake(0, 0, 32.0, 44);
[aKeyboardButton setTitle: [NSString stringWithFormat:#"%ld", (long)i % 10] forState:UIControlStateNormal];
NSString *aButtonImageTitle = [self.layoutController buttonNormalStateImageNameForIndex:(i % 10) forNumericInputAccessory:self];
[aKeyboardButton setBackgroundImage:[UIImage imageNamed:aButtonImageTitle] forState:UIControlStateNormal];
aKeyboardButton.backgroundColor = [UIColor clearColor];
[aKeyboardButton setTitleColor:[UIColor blackColor] forState: UIControlStateNormal];
[aKeyboardButton setExclusiveTouch:YES];
aKeyboardButton.titleLabel.font = [self.layoutController fontForButtonTitlesForNumericInputAccessory:self];
if (self.layoutController.interfaceIdiom == UIUserInterfaceIdiomPhone) {
aKeyboardButton.titleLabel.textAlignment = NSTextAlignmentCenter;
} else {
NSString *aButtonHighlightedImageTitle = [self.layoutController buttonHighlightedStateImageNameForIndex:(i % 10) forNumericInputAccessory:self];
[aKeyboardButton setBackgroundImage:[UIImage imageNamed:aButtonHighlightedImageTitle] forState:UIControlStateHighlighted];
}
aKeyboardButton.hidden = NO;
aKeyboardButton.enabled = YES;
aKeyboardButton.tag = i;
[aKeyboardButton addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:aKeyboardButton];
[buttonsArray addObject:aKeyboardButton];
if (self.layoutController.interfaceIdiom == UIUserInterfaceIdiomPhone) {
UIImage *aPressedButtonImage = [UIImage imageNamed:[self.layoutController buttonHighlightedStateImageNameForIndex:(i % 10) forNumericInputAccessory:self]];
CGRect aPressedButtonViewFrame = [self.layoutController frameForPressedButtonViewWithIndex:(i % 10) forNumericInputAccessory:self];
UIView *aPressedButtonView = [[UIView alloc] initWithFrame:aPressedButtonViewFrame];
aPressedButtonView.backgroundColor = [UIColor clearColor];
aPressedButtonView.userInteractionEnabled = YES;
aPressedButtonView.hidden = YES;
UIImageView *aPressedButtonImageView = [[UIImageView alloc] initWithImage:aPressedButtonImage];
aPressedButtonImageView.frame = CGRectMake(0.0, 0.0, aPressedButtonViewFrame.size.width, aPressedButtonViewFrame.size.height);
aPressedButtonImageView.contentMode = UIViewContentModeBottom;
aPressedButtonImageView.backgroundColor = [UIColor clearColor];
CGRect aPressedButtonLabelFrame = [self.layoutController frameForPressedButtonLabelWithIndex:(i % 10) forNumericInputAccessory:self];
UILabel *aPressedButtonLabel = [[UILabel alloc] initWithFrame:aPressedButtonLabelFrame];
aPressedButtonLabel.backgroundColor = [UIColor clearColor];
aPressedButtonLabel.text = [NSString stringWithFormat: #"%ld", (long)i % 10];
aPressedButtonLabel.textAlignment = NSTextAlignmentCenter;
aPressedButtonView.tag = i + 100;
aPressedButtonLabel.font = [UIFont fontWithName: #"Helvetica-Bold" size: 44.0f];
aPressedButtonLabel.shadowColor = [UIColor colorWithRed: 0.95f green: 0.95f blue: 0.95f alpha: 1.0f];
aPressedButtonLabel.shadowOffset = CGSizeMake(0.0f, 0.75f);
if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication]statusBarOrientation])) {
aPressedButtonImageView.frame = CGRectMake(aPressedButtonImageView.frame.origin.x, aPressedButtonImageView.frame.origin.y - 2.0f, aPressedButtonImageView.frame.size.width, aPressedButtonImageView.frame.size.height);
}
[aPressedButtonView addSubview:aPressedButtonImageView];
[aPressedButtonView addSubview:aPressedButtonLabel];
[self addSubview:aPressedButtonView];
}
}
CALayer *aLightGreyBorder = [CALayer layer];
aLightGreyBorder.frame = CGRectMake(self.bounds.origin.x, self.bounds.origin.y + 1.0f, self.bounds.size.width, 1.0f);
UIColor *aColor = [UIColor colorWithRed:178.0f/255.0f green:184.0f/255.0f blue:191.0f/255.0f alpha:1.0f];
aLightGreyBorder.backgroundColor = [aColor CGColor];
[self.layer insertSublayer:aLightGreyBorder atIndex:0];
CALayer *aDarkGreyBorder = [CALayer layer];
aDarkGreyBorder.frame = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.bounds.size.width, 1.0f);
UIColor *aSecondColor;
aSecondColor = [UIColor colorWithRed:100.0f/255.0f green:100.0f/255.0f blue:100.0f/255.0f alpha:1.0f];
aDarkGreyBorder.backgroundColor = [aSecondColor CGColor];
[self.layer insertSublayer:aDarkGreyBorder atIndex:0];
if (self.layoutController.interfaceIdiom == UIUserInterfaceIdiomPhone) {
for (UIView *aPressedButtonView in [self subviews]) {
if ([aPressedButtonView isMemberOfClass:[UIView class]]) {
[self bringSubviewToFront:aPressedButtonView];
}
}
}
// Now lets add constaints
[self performSelector:#selector(addConstraintsForButtons:) withObject:buttonsArray afterDelay:1.0];
}
Step 3: Add constraints to all buttons (0 to 9). Here, I am showing only first 2 buttons.
- (void)addConstraintsForButtons:(NSMutableArray *)iButtons {
// First Button
MyAccessoryViewNumericButton *firstButton = (MyAccessoryViewNumericButton *)iButtons[0];
// Left edge constraint for First button
NSLayoutConstraint *leading =[NSLayoutConstraint
constraintWithItem:firstButton
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:4.f];
[self addConstraint:leading];
// Middle in the super view
NSLayoutConstraint *vertical =[NSLayoutConstraint
constraintWithItem:firstButton
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterY
multiplier:1.0f
constant:0];
[self addConstraint:vertical];
// Width-Height Constraint for First button
[self addWHContraintForButton:firstButton];
// Second Button
MyAccessoryViewNumericButton *secondButton = (MyAccessoryViewNumericButton *)iButtons[1];
// Left edge constraint for First button
NSLayoutConstraint *secondLeading =[NSLayoutConstraint
constraintWithItem:secondButton
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:firstButton
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:4.f];
[secondButton addConstraint:secondLeading];
// Middle in the super view
NSLayoutConstraint *secondVertical =[NSLayoutConstraint
constraintWithItem:secondButton
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:firstButton
attribute:NSLayoutAttributeCenterY
multiplier:1.0f
constant:0];
[secondButton addConstraint:secondVertical];
// Width-Height Constraint for Second button
[self addWHContraintForButton:secondButton];
// Add constaints to rest of the buttons...
}
- (void)addWHContraintForButton:(MyAccessoryViewNumericButton *)iButton {
CGFloat buttonWidth = 32.0f;
CGFloat buttonHeight = 44.0f;
// Height Constraint
NSLayoutConstraint *height = [NSLayoutConstraint
constraintWithItem:iButton
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:0
constant:buttonHeight];
// Width Constraint
NSLayoutConstraint *width = [NSLayoutConstraint
constraintWithItem:iButton
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:0
constant:buttonWidth];
[iButton addConstraint:height];
[iButton addConstraint:width];
}
But when I run this code, it crashes with following message:
View not found in container hierarchy: <MyAccessoryViewNumericButton: 0x7fb506036f90; baseClass = UIButton; frame = (0 0; 32 44); opaque = NO; tag = 1; layer = <CALayer: 0x7fb506039c70>>
That view's superview: <MyAccessoryView: 0x7fb503ddfb50; frame = (0 0; 320 46); autoresize = W; layer = <CALayer: 0x7fb503dc1fe0>>
This is what I see when debug this (tried by setting constraints in keyboardDidShow: and didMoveToSuperview methods):
(lldb) po [secondButton superview]
<MyAccessoryView: 0x7fcd6d04f6c0; frame = (0 0; 320 46); autoresize = W; layer = <CALayer: 0x7fcd6adf5ed0>>
(lldb) po [[secondButton superview] superview]
<UIInputSetHostView: 0x7fcd6d0346c0; frame = (0 522; 320 262); layer = <CALayer: 0x7fcd6adef270>>
(lldb) po [[[secondButton superview] superview] superview]
<UIInputSetContainerView: 0x7fcd6d014000; frame = (0 0; 320 568); layer = <CALayer: 0x7fcd6add78d0>>
(lldb) po [[[[secondButton superview] superview] superview] superview]
<UITextEffectsWindow: 0x7fcd6d033c30; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x7fcd6d034250>>
(lldb) po [[[[[secondButton superview] superview] superview] superview] superview]
0x0000000000000000
Although view has a parent. Can someone please advise here.

Related

How to have both UILabel and UIButton items into a UIView for UITableView header section

This is my first post here, so I hope to do everything correctly.
I'm trying to write a code in Objective-C that allows me to add a UIButton in a table header view, for a specific section.
This is how it should look:
I see correctly the title, but unfortunately the UIButton doesn't appear and I don't know why.. some months ago I've used a similar code and it worked, but after changing something due to another bug in the table view, it stopped working and unfortunately I haven't the original code anymore..
Do you know where I'm wrong or do you know any alternative?
This is the code in Objective-C (that should automatically align the title and the button, even when the user changes the device orientation and it should work for any screen size):
-(UIView*)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if (section == 2) {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0,0, 0,0)];
[view setClipsToBounds:NO];
[view setPreservesSuperviewLayoutMargins:TRUE];
UILabel *textLabel = [[UILabel alloc] init];
textLabel.text = #"MY TITLE";
textLabel.textColor = [UIColor colorWithRed:0.427f green:0.427f blue:0.427f alpha:1.0f];
//[textLabel setFont:_tableViewFooterFont];
[textLabel setTextAlignment:4]; // Natural
[textLabel setTranslatesAutoresizingMaskIntoConstraints:FALSE];
[textLabel setAccessibilityTraits:UIAccessibilityTraitHeader];
[view addSubview:textLabel];
UIButton *button = [UIButton buttonWithType:1];
//[button setHidden:FALSE];
//[button.titleLabel setFont:_tableViewFooterFont];
[button.titleLabel setTextColor:[UIColor redColor]];
[button setTranslatesAutoresizingMaskIntoConstraints:FALSE];
[button setTitle:#"RESET" forState:0];
[button setContentCompressionResistancePriority:0 forAxis:UILayoutConstraintAxisHorizontal];
[button addTarget:self action:#selector(resetAction:) forControlEvents:UIControlEventTouchUpInside];
[view addSubview:button];
NSDictionary *dictionary = NSDictionaryOfVariableBindings(textLabel, button);
NSLayoutConstraint *buttonConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"|-[textLabel]->=0-[button]-|" options:0 metrics:NULL views:dictionary];
[view addConstraints:buttonConstraint];
NSLayoutConstraint *labelConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-12-[textLabel]-6-|" options:0 metrics:NULL views:dictionary];
[view addConstraints:labelConstraint];
NSLayoutConstraint *viewConstraint = [NSLayoutConstraint constraintWithItem:button attribute:10 relatedBy:0 toItem:textLabel attribute:10 multiplier:1.0 constant:0];
[view addConstraint:viewConstraint];
return view;
}
return NULL;
}
Try this:
-(UIView*)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
// create view for header
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, 40)]; // set header view height as you want
headerView.backgroundColor = [UIColor clearColor] ;
// create label for header title
UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, tableView.frame.size.width-130, 30)];
title.font = [UIFont fontWithName:#"FONT_NAME" size:16.0]; //optional
[title.heightAnchor constraintEqualToConstant:30].active = true;
[title.widthAnchor constraintEqualToConstant:tableView.frame.size.width-130].active = true;
// create button for header action
UIButton *resetButton = [[UIButton alloc] initWithFrame:CGRectMake(self.yourTableView.frame.size.width-120, 0, 120, 30)];
[resetButton setTitle:#"RESET" forState:UIControlStateNormal];
//resetButton.backgroundColor = [UIColor redColor];
[resetButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[resetButton setFont:[UIFont fontWithName:#"FONT_NAME" size:16.0]] ;
[resetButton.heightAnchor constraintEqualToConstant:30].active = true;
[resetButton.widthAnchor constraintEqualToConstant:120].active = true;
//Stack View
UIStackView *stackView = [[UIStackView alloc] initWithFrame:headerView.frame];
stackView.axis = UILayoutConstraintAxisHorizontal;
stackView.distribution = UIStackViewDistributionFillProportionally;
stackView.alignment = UIStackViewAlignmentCenter;
stackView.spacing = 10;
[stackView addArrangedSubview:title];
[stackView addArrangedSubview:resetButton];
stackView.translatesAutoresizingMaskIntoConstraints = false;
[headerView addSubview:stackView];
if(section == 2){
title.text = #"MY TITLE";
[resetButton addTarget:self action:#selector(resetBtnActn:) forControlEvents:UIControlEventTouchUpInside] ;
}else{
headerView.hidden = YES ;
title.hidden = YES ;
resetButton.hidden = YES ;
}
//Trailing
NSLayoutConstraint *trailing =[NSLayoutConstraint
constraintWithItem:stackView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:headerView
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:0.f];
//Leading
NSLayoutConstraint *leading = [NSLayoutConstraint
constraintWithItem:stackView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:headerView
attribute:NSLayoutAttributeLeadingMargin
multiplier:1.0f
constant:0.f];
//Bottom
NSLayoutConstraint *bottom =[NSLayoutConstraint
constraintWithItem:stackView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:headerView
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:0.f];
//Add constraints
[headerView addConstraint:trailing];
[headerView addConstraint:bottom];
[headerView addConstraint:leading];
// 5. Finally return
return headerView;
}
// table section button action
-(IBAction)resetBtnActn:(id)sender
{
NSLog(#"button tapped");
}
comment if you have any issue.

Auto Layout for UIScrollView w/ 2 Child Views

I have a scroll view that's set up inside a storyboard with auto layout constraints so that it fills the entire view. The scroll view contains two views managed by UIViewControllers instantiated from nibs. The code to include these subviews:
- (void)viewDidLoad {
[super viewDidLoad];
ContentViewController *contentVC = [ContentViewController new];
[[NSBundle mainBundle] loadNibNamed:#"ContentView" owner:contentVC options:nil];
self.scrollView.delegate = self;
[self addChildViewController:contentVC];
[self.scrollView addSubview:contentVC.contentTC.view];
[contentVC didMoveToParentViewController:self];
[self addChildViewController:contentVC.contentTC];
self.view.layer.borderColor = [[UIColor blackColor] CGColor];
self.view.layer.borderWidth = 5.0;
self.scrollView.layer.borderColor = [[UIColor blueColor] CGColor];
self.scrollView.layer.borderWidth = 5.0;
CGRect adminFrame = contentVC.view.frame;
adminFrame.origin.x = adminFrame.size.width;
ShareViewController *shareViewController = [[ShareViewController alloc] initWithNibName:#"ShareView" bundle:nil];
[self addChildViewController:shareViewController];
[self.scrollView addSubview:shareViewController.view];
shareViewController.view.frame = adminFrame;
CGRect shareFrame = shareViewController.view.frame;
shareFrame.origin.x = shareFrame.size.width;
[self.view addSubview:self.scrollView];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0]];
// 4) Finally set the size of the scroll view that contains the frames
CGFloat scrollWidth = 2 * self.view.frame.size.width;
CGFloat scrollHeight = self.view.frame.size.height;
self.scrollView.contentSize = CGSizeMake(scrollWidth, scrollHeight);
self.scrollView.pagingEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.bounces = NO;
Don't have the ability to post an image, but here's a link to what it looks like:
dropbox.com/s/e84ya3ffrdzytac/Screenshot%202015-08-19%2011.52.15.png
In the iphone 6+ simulator, the scrollview is smaller in both width and height than the window, so there's white space on the right and bottom.
In the iPhone 4S simulator, the width is correct, but the subviews are longer than the window, which means that they'll scroll down. This only works in the iPhone 5, even though all of the views are inferred and not set up for 4-inch screens.
Apologies if this is a redundant question, but I've been searching through SO and banging my head against the wall for two days now, help would be appreciated.
** EDIT: UPDATED CODE AND BEHAVIOR: **
- (void)viewDidLoad {
[super viewDidLoad];
ContentViewController *contentVC = [ContentViewController new];
[[NSBundle mainBundle] loadNibNamed:#"ContentView" owner:contentVC options:nil];
self.scrollView.delegate = self;
self.scrollView.pagingEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.bounces = NO;
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.scrollView];
UIView *contentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width * 2, self.view.frame.size.height)];
[self.scrollView addSubview:contentView];
[self addChildViewController:contentVC];
[contentView addSubview:contentVC.contentTC.view];
[contentVC didMoveToParentViewController:self];
[self addChildViewController:contentVC.contentTC];
CGRect adminFrame = contentVC.view.frame;
adminFrame.origin.x = adminFrame.size.width;
ShareViewController *shareViewController = [[ShareViewController alloc] initWithNibName:#"ShareView" bundle:nil];
[self addChildViewController:shareViewController];
[contentView addSubview:shareViewController.view];
shareViewController.view.frame = adminFrame;
self.view.layer.borderColor = [[UIColor blackColor] CGColor];
self.view.layer.borderWidth = 3;
self.scrollView.layer.borderColor = [[UIColor blueColor] CGColor];
self.scrollView.layer.borderWidth = 5;
contentView.layer.borderColor = [[UIColor greenColor] CGColor];
contentView.layer.borderWidth = 7;
CGRect shareFrame = shareViewController.view.frame;
shareFrame.origin.x = shareFrame.size.width;
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[v]|" options:0 metrics:nil views:#{#"v" : contentView}]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[v]|" options:0 metrics:nil views:#{#"v" : contentView}]];
Results: dropbox.com/s/29zwwn6inmdvz0e/Screenshot%202015-08-19%2014.27.47.png?dl=0 , dropbox.com/s/rqsdk9yubq84ph6/Screenshot%202015-08-19%2014.28.14.png?dl=0
Here's an image of the constraints from the storyboard: dropbox.com/s/8c5wkfw81sqd8ij/Screenshot%202015-08-19%2014.28.47.png?dl=0 (pinning edges to superview)
bound your scrollview from left,right,top and bottom then it look fine.
and it will look like..
here is code that i written with yours
(void)viewDidLoad
{
[super viewDidLoad];
UIViewController *contentVC = [[UIViewController alloc]init];
contentVC.view.backgroundColor = [UIColor greenColor];
self.scrollView.delegate = self;
[self addChildViewController:contentVC];
[self.scrollView addSubview:contentVC.view];
[contentVC didMoveToParentViewController:self];
[self addChildViewController:contentVC];
self.view.layer.borderColor = [[UIColor blackColor] CGColor];
self.view.layer.borderWidth = 5.0;
self.scrollView.layer.borderColor = [[UIColor blueColor] CGColor];
self.scrollView.layer.borderWidth = 5.0;
CGRect adminFrame = contentVC.view.frame;
adminFrame.origin.x = adminFrame.size.width;
UIViewController *shareViewController = [[UIViewController alloc] init];
shareViewController.view.backgroundColor = [UIColor grayColor];
[self addChildViewController:shareViewController];
[self.scrollView addSubview:shareViewController.view];
shareViewController.view.frame = adminFrame;
CGRect shareFrame = shareViewController.view.frame;
shareFrame.origin.x = shareFrame.size.width;
[self.view addSubview:self.scrollView];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0]];
// 4) Finally set the size of the scroll view that contains the frames
CGFloat scrollWidth = 2 * self.view.frame.size.width;
CGFloat scrollHeight = self.view.frame.size.height;
self.scrollView.contentSize = CGSizeMake(scrollWidth, scrollHeight);
self.scrollView.pagingEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.bounces = NO;
}

Layout of custom UITableViewCell using Autolayout programmatically

I have a custom UITableViewCell that works great on iPhone 5s, however since this projected started we now have to update it to support iPhone 6 en iPhone 6 Plus as well.
I've solved most of these upgrade issues using Autolayout in my .xib files. This Cell however does not have a .xib so I Have to do everything programmatically. For clarity I've temporarily drawn a border around my problematic label
This is how it looks on iPhone 5S
This is how it looks on iPhone 6
These tableviews are expandable/collapsable. In the screenshot you see it expanded.
This is the relevant code:
#import "LotsDetailTableViewCell.h"
#import "MSCellAccessory.h"
#interface LotsDetailTableViewCell ()
#property (nonatomic, strong) NSArray *data;
#property (nonatomic, strong) UIView *detailView;
#end
const float LABEL_HEIGHT = 22.0f;
#implementation LotsDetailTableViewCell
- (id)initWithData:(NSArray *)data reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
if (self) {
self.data = data;
[self setupDetailView];
self.textLabel.textColor = RGB(39, 143, 191);
self.textLabel.font = [UIFont fontWithName:#"HelveticaNeue-Light" size:16.0];
self.autoresizingMask = UIViewAutoresizingFlexibleHeight;
self.clipsToBounds = true;
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.textLabel.frame = CGRectMake(self.textLabel.frame.origin.x,
self.textLabel.frame.origin.y,
self.textLabel.frame.size.width,
43.5f);
self.accessoryView.frame = CGRectMake(self.accessoryView.frame.origin.x, 6, 32, 32);
}
#pragma mark Detail View
- (void)setupDetailView
{
self.detailView = [[UIView alloc] init];
UILabel *previousLabel;
for (NSObject *detail in self.data) {
CGFloat frameY;
NSString *keyText;
NSString *valueText;
if([detail isKindOfClass:[NSString class]]){
keyText = #"";
valueText = (NSString *)detail;
} else if([detail isKindOfClass:[NSDictionary class]]){
keyText = ((NSDictionary *)detail)[#"key"];
valueText = [(NSDictionary *)detail objectForKey:#"value"];
}
if (previousLabel) {
frameY = previousLabel.frame.origin.y+previousLabel.frame.size.height;
} else {
frameY = self.frame.origin.y+44.0f+[self.data indexOfObject:detail]*LABEL_HEIGHT;
}
UILabel *keyLabel = [[UILabel alloc] initWithFrame:
CGRectMake(self.frame.size.width*.05, frameY, self.frame.size.width*.45, [self heightForValueLabel:valueText])
];
UILabel *valueLabel = [[UILabel alloc] initWithFrame:
CGRectMake(self.frame.size.width*.5, frameY, self.frame.size.width*.45, [self heightForValueLabel:valueText])
];
valueLabel.lineBreakMode = NSLineBreakByWordWrapping;
valueLabel.numberOfLines = 0;
keyLabel.lineBreakMode = NSLineBreakByWordWrapping;
keyLabel.numberOfLines = 0;
valueLabel.textAlignment = NSTextAlignmentRight;
keyLabel.font = [UIFont fontWithName:#"HelveticaNeue-Light" size:12.0];
valueLabel.font = [UIFont fontWithName:#"HelveticaNeue-Bold" size:12.0];
keyLabel.textColor = RGB(119, 119, 119);
valueLabel.textColor = RGB(39, 143, 191);
keyLabel.text = keyText;
valueLabel.text = valueText;
valueLabel.layer.borderColor = [RGB(178, 178, 178) CGColor];
valueLabel.layer.borderWidth = 1.0f;
[self.detailView addSubview:keyLabel];
[self.detailView addSubview:valueLabel];
previousLabel = valueLabel;
}
[self addSubview:self.detailView];
}
- (CGFloat)heightForValueLabel:(NSString *)text
{
return [[self class] heightForValueLabel:text width:self.frame.size.width*.5];
}
+ (CGFloat)heightForValueLabel:(NSString *)text width:(CGFloat)width
{
CGSize constrainedSize = CGSizeMake(width, CGFLOAT_MAX);
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
[UIFont fontWithName:#"HelveticaNeue-Bold" size:12.0], NSFontAttributeName,
nil];
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:text attributes:attributesDictionary];
CGRect requiredHeight = [string boundingRectWithSize:constrainedSize options:NSStringDrawingUsesLineFragmentOrigin context:nil];
return requiredHeight.size.height+5.0;
}
+ (CGFloat)heightForData:(NSArray*)data width:(CGFloat)width
{
CGFloat height = 0.0;
for (NSObject *detail in data) {
NSString *valueText;
if([detail isKindOfClass:[NSString class]]){
valueText = (NSString *)detail;
} else if([detail isKindOfClass:[NSDictionary class]]){
valueText = ((NSDictionary *)detail)[#"value"];
}
height = height + [self heightForValueLabel:valueText width:width];
}
return height;
}
#end
Most of the relevant drawing is performed in the setupDetailView method.
What I want: I want the label to be aligned to the right hand side of the tableviewcell to make best use of the available space.
What I tried: I've tried adding a NSLayoutConstraint programmatically in the setupDetailView using the following co
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:valueLabel attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1.0 constant:1.0];
[valueLabel addConstraint:constraint];
[self needsUpdateConstraints];
but this has lead to run-time crashes.
If my problem can be easily solved without Autolayout I'm not opposed to that.
Edit: If I try to add the constraint, this is how it blows up:
2015-03-25 14:19:04.047 App[35277:10763464] The view hierarchy is not prepared for the constraint: <NSLayoutConstraint:0x7faf5049a630 UILabel:0x7faf50499a50'8050'.trailing == LotsDetailTableViewCell:0x7faf50494840'LotDetailInfoCell'.right + 1>
When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView _viewHierarchyUnpreparedForConstraint:] to debug.
2015-03-25 14:19:04.048 App[35277:10763464] View hierarchy unprepared for constraint.
Constraint: <NSLayoutConstraint:0x7faf5049a630 UILabel:0x7faf50499a50'8050'.trailing == LotsDetailTableViewCell:0x7faf50494840'LotDetailInfoCell'.right + 1>
Container hierarchy:
<LotsDetailTableViewCell: 0x7faf50494840; baseClass = UITableViewCell; frame = (0 0; 320 44); layer = <CALayer: 0x7faf50494c50>>
| <UITableViewCellContentView: 0x7faf5049c070; frame = (0 0; 320 44); gestureRecognizers = <NSArray: 0x7faf504a2530>; layer = <CALayer: 0x7faf50455b60>>
View not found in container hierarchy: <UILabel: 0x7faf50499a50; frame = (156 44; 144 19.304); text = '8050'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7faf50499870>>
That view's superview: NO SUPERVIEW
2015-03-25 14:19:04.058 App[35277:10763464] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to install constraint on view. Does the constraint reference something from outside the subtree of the view? That's illegal. constraint:<NSLayoutConstraint:0x7faf5049a630 UILabel:0x7faf50499a50'8050'.trailing == LotsDetailTableViewCell:0x7faf50494840'LotDetailInfoCell'.right + 1> view:<LotsDetailTableViewCell: 0x7faf50494840; baseClass = UITableViewCell; frame = (0 0; 320 44); layer = <CALayer: 0x7faf50494c50>>'
Give leftConstarint to your label, after adding it as subView.
[_label setTranslatesAutoresizingMaskIntoConstraints:NO];
NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:_label
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:[_label superview]
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:10];
[_label addConstraint:leftConstraint];
[_label superview] must be your [self view]. //Might be.
Hope, it helps. :)
When adding constraints between subview and superview, you have to add them to the superview:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:valueLabel attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1.0 constant:1.0];
[self addConstraint:constraint];
[self needsUpdateConstraints];

UILabel/Content not appearing in UIScrollView

been trying to figure this out for a while. I'm tapping on the CNPPopupController and I'm trying to add a UIScrollView into the UIView.
In the scroll view I will be adding the content, I have tried so far but the content does not seem to be appearing in the UIScrollView. I am not sure what I'm doing wrongly, can anyone please guide me?
//The content view that will house labels and a UIScrollView
self.contentView = [[UIView alloc] init];
[self.contentView setTranslatesAutoresizingMaskIntoConstraints:NO];
self.contentView.clipsToBounds = YES;
self.contentView.backgroundColor = self.theme.backgroundColor;
self.contentView.layer.cornerRadius = self.theme.popupStyle == CNPPopupStyleCentered ? self.theme.cornerRadius : 0.0f;
[self.maskView addSubview:self.contentView];//The UI ScrollView
self.scrollView = [[UIScrollView alloc] init];
//self.scrollView.backgroundColor= self.theme.backgroundColor;
self.scrollView.frame = CGRectMake(50, 50, 200, 100);
self.scrollView.layer.cornerRadius = self.theme.popupStyle == CNPPopupStyleCentered ? self.theme.cornerRadius : 0.0f;
self.scrollView.clipsToBounds = YES; // default is NO, we want to restrict drawing within our scrollview
self.scrollView.scrollEnabled = YES;
if (self.popupTitle) {
UILabel *title = [self multilineLabelWithAttributedString:self.popupTitle];
[self.contentView addSubview:title];
}
//Adding items in the UISCrollView
if (self.contents) {
for (NSObject *content in self.contents) {
if ([content isKindOfClass:[NSAttributedString class]]) {
UILabel *label = [self multilineLabelWithAttributedString:(NSAttributedString *)content];
[label sizeToFit];
CGSize scrollViewContentSize = CGSizeMake(self.contentView.frame.size.width, self.contentView.frame.size.height+500);
[self.scrollView setContentSize:scrollViewContentSize];
[self.scrollView addSubview:label];
}
else if ([content isKindOfClass:[UIImage class]]) {
UIImageView *imageView = [self centeredImageViewForImage:(UIImage *)content];
[imageView sizeToFit];
CGSize scrollViewContentSize = CGSizeMake(self.contentView.frame.size.width, self.contentView.frame.size.height+500);
[self.scrollView setContentSize:scrollViewContentSize];
[self.scrollView addSubview:imageView];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationLessThanOrEqual toItem:imageView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0]];
}
}
[self.contentView addSubview:self.scrollView];
}

How to use AutoLayout to position UIButtons in horizontal lines (wrapping, left aligned)?

I need to create a couple of UIButtons with various widths programmatically in my app (iOS 6.0 and above).
I want to display the buttons in a "wrap around" style: Starting from the left edge, each button should be positioned next to each other horizontally (in a defined order), and if a button does not fit in the current "line", it should start a new line on the left edge below the previous line.
Note: I don't want a table/grid, since the buttons have different widths, and I want to have one right next to each other.
I could manually calculate the frame of each button in my code, but should I use AutoLayout (with programmatically created NSLayoutConstraints) instead? How exactly would I need to set it up?
EDIT: After reading through Chapter 4 "Intermediate Auto Layout" of "iOS 6 by Tutorials" I am not sure whether using pure AutoLayout could implement this "wrap around" functionality I require.
My current solution looks like this: No AutoLayout, but manually setting the correct constraints for each case (first button, leftmost button in a new line, any other button).
(My guess is that setting the frame for each button directly would result in more readable code than using NSLayoutConstraints, anyway)
NSArray *texts = #[ #"A", #"Short", #"Button", #"Longer Button", #"Very Long Button", #"Short", #"More Button", #"Any Key"];
int indexOfLeftmostButtonOnCurrentLine = 0;
NSMutableArray *buttons = [[NSMutableArray alloc] init];
float runningWidth = 0.0f;
float maxWidth = 300.0f;
float horizontalSpaceBetweenButtons = 10.0f;
float verticalSpaceBetweenButtons = 10.0f;
for (int i=0; i<texts.count; i++) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button setTitle:[texts objectAtIndex:i] forState:UIControlStateNormal];
[button sizeToFit];
button.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:button];
// check if first button or button would exceed maxWidth
if ((i == 0) || (runningWidth + button.frame.size.width > maxWidth)) {
// wrap around into next line
runningWidth = button.frame.size.width;
if (i== 0) {
// first button (top left)
// horizontal position: same as previous leftmost button (on line above)
NSLayoutConstraint *horizontalConstraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0f constant:horizontalSpaceBetweenButtons];
[self.view addConstraint:horizontalConstraint];
// vertical position:
NSLayoutConstraint *verticalConstraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:verticalSpaceBetweenButtons];
[self.view addConstraint:verticalConstraint];
} else {
// put it in new line
UIButton *previousLeftmostButton = [buttons objectAtIndex:indexOfLeftmostButtonOnCurrentLine];
// horizontal position: same as previous leftmost button (on line above)
NSLayoutConstraint *horizontalConstraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:previousLeftmostButton attribute:NSLayoutAttributeLeft multiplier:1.0f constant:0.0f];
[self.view addConstraint:horizontalConstraint];
// vertical position:
NSLayoutConstraint *verticalConstraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:previousLeftmostButton attribute:NSLayoutAttributeBottom multiplier:1.0f constant:verticalSpaceBetweenButtons];
[self.view addConstraint:verticalConstraint];
indexOfLeftmostButtonOnCurrentLine = i;
}
} else {
// put it right from previous buttom
runningWidth += button.frame.size.width + horizontalSpaceBetweenButtons;
UIButton *previousButton = [buttons objectAtIndex:(i-1)];
// horizontal position: right from previous button
NSLayoutConstraint *horizontalConstraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:previousButton attribute:NSLayoutAttributeRight multiplier:1.0f constant:horizontalSpaceBetweenButtons];
[self.view addConstraint:horizontalConstraint];
// vertical position same as previous button
NSLayoutConstraint *verticalConstraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:previousButton attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f];
[self.view addConstraint:verticalConstraint];
}
[buttons addObject:button];
}
Instead of using Autolayout, you could just use a collection view which better options for you to lay out elements such as buttons.
It is better able to handle layouts under rotation as well.
Here is the another example of how we can implement wrapping layout with auto layout:
#interface SCHorizontalWrapView : UIView
#property(nonatomic)NSMutableArray *wrapConstrains;
#end
#implementation SCHorizontalWrapView {
CGFloat intrinsicHeight;
BOOL updateConstraintsCalled;
}
-(id)init {
self = [super init];
if (self) {
[UIView autoSetPriority:UILayoutPriorityDefaultHigh forConstraints:^{
[self autoSetContentCompressionResistancePriorityForAxis:ALAxisVertical];
[self autoSetContentCompressionResistancePriorityForAxis:ALAxisHorizontal];
[self autoSetContentCompressionResistancePriorityForAxis:ALAxisHorizontal];
[self autoSetContentCompressionResistancePriorityForAxis:ALAxisVertical];
}];
}
return self;
}
-(void)updateConstraints {
if (self.needsUpdateConstraints) {
if (updateConstraintsCalled == NO) {
updateConstraintsCalled = YES;
[self updateWrappingConstrains];
updateConstraintsCalled = NO;
}
[super updateConstraints];
}
}
-(NSMutableArray *)wrapConstrains {
if (_wrapConstrains == nil) {
_wrapConstrains = [NSMutableArray new];
}
return _wrapConstrains;
}
-(CGSize)intrinsicContentSize {
return CGSizeMake(UIViewNoIntrinsicMetric, intrinsicHeight);
}
-(void)setViews:(NSArray*)views {
if (self.wrapConstrains.count > 0) {
[UIView autoRemoveConstraints:self.wrapConstrains];
[self.wrapConstrains removeAllObjects];
}
NSArray *subviews = self.subviews;
for (UIView *view in subviews) {
[view removeFromSuperview];
}
for (UIView *view in views) {
view.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:view];
CGFloat leftPadding = 0;
[view autoSetDimension:ALDimensionWidth toSize:CGRectGetWidth(self.frame) - leftPadding relation:NSLayoutRelationLessThanOrEqual];
}
}
-(void)updateWrappingConstrains {
NSArray *subviews = self.subviews;
UIView *previewsView = nil;
CGFloat leftOffset = 0;
CGFloat itemMargin = 5;
CGFloat topPadding = 0;
CGFloat itemVerticalMargin = 5;
CGFloat currentX = leftOffset;
intrinsicHeight = topPadding;
int lineIndex = 0;
for (UIView *view in subviews) {
CGSize size = view.intrinsicContentSize;
if (previewsView) {
[self.wrapConstrains addObject:[view autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:topPadding relation:NSLayoutRelationGreaterThanOrEqual]];
[self.wrapConstrains addObject:[view autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:leftOffset relation:NSLayoutRelationGreaterThanOrEqual]];
CGFloat width = size.width;
currentX += itemMargin;
if (currentX + width <= CGRectGetWidth(self.frame)) {
[self.wrapConstrains addObject:[view autoConstrainAttribute:ALEdgeLeading toAttribute:ALEdgeTrailing ofView:previewsView withOffset:itemMargin relation:NSLayoutRelationEqual]];
[self.wrapConstrains addObject:[view autoAlignAxis:ALAxisBaseline toSameAxisOfView:previewsView]];
currentX += size.width;
}else {
[self.wrapConstrains addObject: [view autoConstrainAttribute:ALEdgeTop toAttribute:ALEdgeBottom ofView:previewsView withOffset:itemVerticalMargin relation:NSLayoutRelationGreaterThanOrEqual]];
currentX = leftOffset + size.width;
intrinsicHeight += size.height + itemVerticalMargin;
lineIndex++;
}
}else {
[self.wrapConstrains addObject:[view autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:topPadding relation:NSLayoutRelationEqual]];
[self.wrapConstrains addObject:[view autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:leftOffset relation:NSLayoutRelationEqual]];
intrinsicHeight += size.height;
currentX += size.width;
}
[view setNeedsUpdateConstraints];
[view updateConstraintsIfNeeded];
[view setNeedsLayout];
[view layoutIfNeeded];
previewsView = view;
}
[self invalidateIntrinsicContentSize];
}
#end
Here I'm using PureLayout for defining constrains.
You can use this class like this:
SCHorizontalWrapView *wrappingView = [[SCHorizontalWrapView alloc] initForAutoLayout];
//parentView is some view
[parentView addSubview:wrappingView];
[tagsView autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:padding];
[tagsView autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:padding];
[tagsView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:locationView withOffset:padding relation:NSLayoutRelationGreaterThanOrEqual];
[tagsView setNeedsLayout];
[tagsView layoutIfNeeded];
[tagsView setNeedsUpdateConstraints];
[tagsView updateConstraintsIfNeeded];
NSMutableArray *views = [NSMutableArray new];
//texts is some array of nsstrings
for (NSString *text in texts) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.translatesAutoresizingMaskIntoConstraints = NO;
[button setTitle:text forState:UIControlStateNormal];
button.backgroundColor = [UIColor lightGrayColor];
[views addObject:button];
}
[tagsView setViews:views];

Resources