Autolayout programmatically on a subview of a subclassed UIView - iphone

I have subclassed UIView to make a custom groupView I use to add a few things to my layout in a simple way. This groupView includes a UILabel which is used as a heading and a UIView that draws a roundRect on it's CALayer with a background color. Think UITableView's sections. I add this groupView to the storyboard by dropping a UIView and changing it's class to my subclass. All works well, I set the heading via the User defined runtime attributes in Xcode. All works great, I add UILabels to this view on the storyboard and it creates the heading label and roundrect when it runs.
structure of my groupView:
groupView: (UIView)clipToBounds:NO;
heading: (UILabel) positioned
above the groupView.
contentView:(UIView) creates the roundRect
and color via CALayer, should be same size as the groupView.
So what's the problem? Well, dealing with autolayout is a pain to begin with, but for this subclassed UIView to work I need to set the contentView constraints programmatically. I can't figure out the syntax of this auto layout ASCII format string. Currently I have:
_contentView = [[UIView alloc]initWithFrame:self.bounds];
_contentView.layer.cornerRadius = 5.0f;
_contentView.layer.masksToBounds=YES;
_contentView.backgroundColor=_backgroundColor;
_contentView.layer.borderWidth=_borderWidth;
_contentView.layer.borderColor=_borderColor.CGColor;
[self insertSubview:_contentView atIndex:0];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(self,_contentView);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"[self]-0-[_contentView]-0-[self]" options:0 metrics:nil views:viewsDictionary];
for (NSLayoutConstraint *constraint in constraints) {
[_contentView addConstraint:constraint];
}
Which crashes with: * 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: view:>'
I tried this first and it still didn't work:
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_contentView);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-0-[_contentView]-0-|" options:0 metrics:nil views:viewsDictionary];
Which crashes with: * 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: view:>'
RANT: Somehow this AutoLayout is suppose to save us work, but
I do not see how the benefits out weight the overhead right now. Why
on earth did they move from using references and methods or even type
defs to this archaic format string? How much easier would it be to do:
[_contentView constraint:NSLayoutFormatAlignLeading toView:self
withDistance:0.0f]; OR something similar? I would so much rather deal
with springs and struts at this point.
Any help understanding, or showing me the syntax to constrain the contentView to the size of self would be helpful.

The error tells you what you need to know:
Does the constraint reference something from outside the subtree of the view?
Yes it does. It references its superview.
These constraints you are making need to be applied to the superview, in this case the groupView.

The | character represents the superview in the visual format language, so I think you want:
#"|-0-[_contentView]-0-|"
and then you need to add the constraint to self instead of the contentView, as you need to add it to a view which can see all of the players involved.
Here is the reference for the visual language:
https://developer.apple.com/library/mac/#documentation/UserExperience/Conceptual/AutolayoutPG/Articles/formatLanguage.html#//apple_ref/doc/uid/TP40010853-CH3-SW1
You can also avoid the visual format entirely by using:
+(id)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c

try check the constrints , whether they are right add to the view
eg:
UIView *yellowView = [[UIView alloc] init];
yellowView.backgroundColor = [UIColor yellowColor];
yellowView.translatesAutoresizingMaskIntoConstraints = false;
[self.view addSubview:yellowView];
//ios 9 last
NSLayoutConstraint *heightAnchor = [yellowView.heightAnchor constraintEqualToAnchor:self.purView.heightAnchor];
// addConstraints
[self.view addConstraints:#[heightAnchor]];
[self.view setNeedsLayout];
if add heightAnchor to yellowView like follow . it will show the error
[yellowView addConstraints:#[heightAnchor]];

Related

Adding constraint to a UITableVIew headerview

So I'm new to the constraints.
I've a nib file which contains multiple Views as siblings. The ViewController's view contains a tableView and I've another view which is going to be added to the tableHeaderView (let's call it self.tableHeaderView).
The problem i'm facing is that I want to resize the self.tableHeaderView based on certain conditions. I've added constraints to all of my UI elements and I can't, for whatever reason, add a height constraint to the self.tableHeaderView via the nib.
I tried changing the frame of the self.tableHeaderView programmatically but that has no effect when i run the code in simulator, which makes sense because if I use Auto-layout, it should ignore the frame changes.
I tried adding a height constraint programmatically but it crashes.
This is the piece of code I've to add the height constraint.
[self.tableHeaderView addConstraint:[NSLayoutConstraint constraintWithItem:self.tableHeaderView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:107.0f]];
The exception i got:
*** Assertion failure in -[UITableView layoutSublayersOfLayer:], /SourceCache/UIKit_Sim/UIKit-2903.2/UIView.m:8536
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super.'*
Worst case scenario I'll add another sibling view with the second height and duplicate the UI elements but i want to avoid this.
Edit 1: I get that exception when i've this
self.topicHeaderView.translatesAutoresizingMaskIntoConstraints = NO;
If i don't have it, i get this
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"",
""
)
Will attempt to recover by breaking constraint
Break on objc_exception_throw to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in may also be helpful.
Edit: 2
On a 4" screen, it looks fine (The red background covers the entire tableHeaderView as i would expect)
On a 3.5" screen, the red background (which is applied on the nib, extends to a certain height even though i set the height to be 117.0f. The UI elements that are inside the tableHeaderView shows up correctly)
The blue line at the bottom is the separator line and the blue border is around the tableHeaderView.
When you are adding a view as a header or footer to the table view, you cannot use constraints on this view, but only inside it. Also the view must be on the top of the hierarchy (as you have), if you move it as a subview to another view, it will give the same error.
You can change view's height directly in code by setting the same frame with changed height. This is working fine.
Also remember that this changes will not apply until you reassign the header:
tableView.tableHeaderView.frame = ...;
tableView.tableHeaderView = tableView.tableHeaderView;
I had the same issue, I couldn't use constraints on the tableHeaderView.
Then I created another subView to create the constraints into.
// 1 - create a header view and a subHeaderView
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, bounds.size.width, bounds.size.height - 64.f)];
headerView.backgroundColor = [UIColor blackColor];
tableView.tableHeaderView = headerView;
UIView *subHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, bounds.size.width, bounds.size.height - 64.f)];
[headerView addSubview:subHeaderView];
// 2 - add constrainedView to subHeaderView (E.g.)
UIView *constrainedView = [UIView new];
[constrainedView setTranslatesAutoresizingMaskIntoConstraints:NO];
[subHeaderView addSubView:constrainedView];
// 3 - addConstraints of subviews into subHeaderView (E.g.)
[subHeaderView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[constrainedView]|" options:0 metrics:metrics views:views]];
[subHeaderView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[constrainedView]|" options:0 metrics:metrics views:views]];
This is working on my project, iOS 7.0 / Xcode 5.0.2

Drawing in a nested UIView

I am trying to make a drawing application following this tutorial: http://www.effectiveui.com/blog/2011/12/02/how-to-build-a-simple-painting-app-for-ios/, and then I tried to make it such that I don't only draw on the entire screen, I only draw on a UIView that is inside of another UIView. (What I call a nested UIView)
My code is currently on github: https://github.com/sammy0025/SimplePaint
The only parts I tweaked with the original code from the tutorial is to change some class prefix names, enabled ARC so no deallocs, used storyboards (which works fine with the original code from the tutorial), and change this code in the main view controller implementation file (SPViewController.m):
-(void)viewDidLoad
{
[super viewDidLoad];
nestedView.backgroundColor=[UIColor colorWithWhite:0 alpha:0]; //This is to make the nested view transparent
SPView *paint = [[SPView alloc] initWithFrame:nestedView.bounds]; //original code is SPView *paint=[[SPView alloc] initWithFrame:self.view.bounds];
[nestedView addSubview:paint]; //original code is [self.view addSubview:paint];
}
My question is how do I make sure that I only draw inside the nested UIView?
Add clipping to your sub view. E.g. self.bounds would cover the whole area of a view.
UIBezierPath *p = [UIBezierPath bezierPathWithRect:self.bounds];
[p addClip];
I believe clipping for a view should be based on bounds initially and that you may shrink it by adding an new clipping. But there might be a difference between iOS and OS X here.
Rather than using an SPView inside a nested view, consider just changing the frame of the SPView to match what you want. This should solve your problem of only allowing drawing within a given rect.
I think you're seeing issues because of your storyboard configuration. I dont know much about storyboard, but I was able to get this to work programmatically by throwing out storyboard's view and building my own in viewDidAppear.
-(void)viewDidAppear:(BOOL)animated{
UIView *newView = [[UIView alloc] initWithFrame:self.view.bounds];
newView.backgroundColor = [UIColor greenColor];
SPView *newPaint = [[SPView alloc] initWithFrame:CGRectInset(self.view.bounds, 40,40)];
[newView addSubview:newPaint];
self.view = newView;
}

UIScrollView referenced both by UIViewController's 'view' property and by an outlet

I'm about to add a UIScrollView to my iPhone project and before I implement this functionality I wanted to check if my approach is the right one or if I could be violating some best practice I'm not aware of.
The tutorials I've seen generally involve adding a UIScrollView to an existing UIView and they work from there. However, I was wondering if I could spare the UIView altogether and just add the UIScrollView as the only top-level object in my nib file.
My sample project uses Xcode's View-based Application template:
Project navigator http://img542.imageshack.us/img542/5364/projectnavigator.png
I deleted the UIView top-level object from the original MySampleViewController.xib file and replaced it by adding a UIScrollView object:
Nib placeholders http://img526.imageshack.us/img526/7709/placeholderobjects.png
Now my nib file only shows this object in the canvas:
Canvas http://img233.imageshack.us/img233/4063/scrollview.png
Then I created the link from the UIViewController's view outlet to the UIScrollView.
Now, if I wanted to programmatically manipulate the contents of the UIScrollView I can use this code:
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *colors = [NSArray arrayWithObjects:[UIColor redColor], [UIColor greenColor], [UIColor blueColor], nil];
// Solution B: With the following line we avoid creating an extra outlet linking to the UIScrollView top-level object in the nib file
UIScrollView *scrollView = (UIScrollView *)self.view;
for (int i = 0; i < colors.count; i++) {
CGRect frame;
//frame.origin.x = self.scroller.frame.size.width * i; // Solution A: scroller is an IBOutlet UIScrollView *scroller;
frame.origin.x = scrollView.frame.size.width * i; // Solution B
frame.origin.y = 0;
//frame.size = self.scroller.frame.size; // Solution A
frame.size = scrollView.frame.size; // Solution B
UIView *subView = [[UIView alloc] initWithFrame:frame];
subView.backgroundColor = [colors objectAtIndex:i];
//[self.scroller addSubview:subView]; // Solution A
[self.view addSubview:subView]; // Solution B
[subView release];
}
//self.scroller.contentSize = CGSizeMake(self.scroller.frame.size.width * colors.count, self.scroller.frame.size.height); // Solution A
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * colors.count, scrollView.frame.size.height); // Solution B
}
In order to implement Solution A the scroller outlet must be linked to the nib's UIScrollView as well, and the Connections Inspector looks like this:
Connections Inspector http://img228.imageshack.us/img228/8397/connectionsj.png
Solution A requires an outlet and this means having two connections to the UIScrollView: the UIViewController's own view outlet and MySampleViewController's scroller outlet. Is it legal and/or recommended to have two outlets pointing to the same view?
Solution B only involves UIViewController's view outlet linking to the view, and using this line:
UIScrollView *scrollView = (UIScrollView *)self.view;
My questions:
Do I incur in some sort of violation of Apple's design guidelines by using one of these two solutions?
Should I stick to the UIScrollView within a UIView solution?
Is there any other way to implement this?
Thanks!
P.S. Sorry for the syntax highlight, SO didn't recognize the use of the 'objective-c' tag
No I think you are fine either way.
I would, I don't think a UIView has any significant cost, plus what if you want to add a page control? and you don't have to cast the controller's view to a UIScrollView every time you need it.
Looks like you have it under control to me.
Solution A requires an outlet and this means having two connections to the UIScrollView: the UIViewController's own view outlet and MySampleViewController's scroller outlet. Is it legal and/or recommended to have two outlets pointing to the same view?
It standard to have IBOutlets to any view defined in your .nib that you want to access directly from your view controller.
If you don't want two outlets you could give the scroll view a tag then find it like so:
UIScrollView *myScrollView = (UIScrollView *)[self.view viewWithTag:1]
Then you only have the view as an outlet, but I would just add the extra outlet. Just make sure you set them to nil in your viewDidUnload.
Also you don't have to retain the scroll view (if you are even still using retain/release). Since the scroll view is inside your view controller's view it keeps a reference so you can have your scrollview's property by assign or week if your using ARC.
Hope that helps.

Custom UITableView headerView disappears after memory warning

I have a UITableViewController. I create a custom headerView for it's tableView in the loadView method like so:
(void)loadView {
[super loadView];
UIView* containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, height * 2 )];
containerView.tag = 'cont';
containerView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(padding, height, width, height);
... //configure UIButton and events
UIImageView* imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"image.png"] highlightedImage:[UIImage imageNamed:#"highlight.png"]];
imageView.frame = CGRectMake(0, 0, width, height );
... //configure UIImageView
[containerView addSubview:button];
[containerView addSubview:imageView];
self.tableView.tableHeaderView = containerView;
[imageView release];
[containerView release];
}
None of the other methods (viewDidLoad/Unload, etc) are overloaded.
This controller is hosted in a tab. When I switch to another tab and simulate a memory warning, and then come back to this tab, my UITableView is missing my custon header. All the rows/section are visible as I would expect. Putting a BP in the loadView code above, I see it being invoked when I switch back to the tab after the memory warning, and yet I can't actually see the header.
Any ideas about what I'm missing here?
EDIT: This happens on the device and the simulator. On the device, I just let a memory warning occur by opening a bunch of different apps while mine is in the background.
the loadView method shouldn't call super. Try removing [super loadView]; and that may well solve your problem. The other overridden view methods (viewDidLoad, viewWillAppear etc.) can call super just fine. See the documentation at:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html
for confirmation.
Keep containView instead of releasing it. And return it in tableView:viewForHeaderInSection: (UITableViewDelegate method).
I had a similar problem to this, though it doesn't seem to explain the OP's situation based on his code; I just want to put it here in case anyone else comes across this.
I retain my header view in an instance variable. In my viewDidLoad I check to see if this variable is nil, if it is then I create the view, retain and set the instance variable to it and set tableHeaderView to it. The problem is that this condition will only be true once, because my instance variable will never be nil after the first time I create it; however, the table view's tableHeaderView property will be set to nil when there's a memory warning, so there won't be a header view any more, even though I still have the view in an instance variable.
The solution was either to change the check to see if the table view's tableHeaderView's property is nil (and then re-create the header view every time that gets set to nil), or (since in this case I still have the view retained in an instance variable) to move the assignment to tableHeaderView outside of the if-block, thus every time viewDidLoad is run, we will make sure to re-assign the header view to tableHeaderView in case it got set to nil.
What are width and height? Is it possible that they are zero after a memory warning?
that is normal way, iOS use this way to reduce memory, while receive memory issue, then it will free some UI element , that also will invoke method 'didUnLoad' ... you have to free the headerView in the method 'didUnLoad' , set to nil , and then in the method 'didLoad' to create the headerView if it is nil and then add it to tableView as well....
iOS will automatic free some UI elements while face memory warning .
I just ran into a similar issue that was causing my custom header for the uitableview to not be reloaded after receiving a memory warning. my current implementation to create the header is
- (void)viewDidLoad
{
[super viewDidLoad];
if (_tableHeaderView == nil)
{
[[NSBundle mainBundle] loadNibNamed:#"DetailHeaderView" owner:self options:nil];
self.tableView.tableHeaderView = _tableHeaderView;
}
}
and to fix the problem i simply added this line to my didReceiveMemoryWarning
_tableHeaderView = nil
and now it is reloading after a memory warning is sent.
Sorry if this has already been answered but I didn't see a clear cut answer for my problem and figured I'd pop this in here for anyone else in my situation.
Happy Coding
Doing a reloadData on the table view might help
The only problem that I can see by looking at just your loadView is that you are trying to tag your containerView with a string. That should be an NSInteger as far as I know.
This problem is known. For example, if you use the TableViewDelegate tableView:viewForHeaderInSection: it won't work, too.
The problem is iOS and not you. There aren't many questions about the tableHeaderView because nobody use it, and so Apple didn't get any BugTracker entries...
Have a look to a older question: here. And here are other problems.
So I think really it's a SDK bug...

iphone, objective c, xcode

I defined a UIView "RowOfThree" inwhich there are 3 labels. i also defined a UIView "Table" inwhich there are numer of objects of type "Row".
the following code is in a method within object "Table":
RowOfThree *rowOfThree = [[RowOfThree alloc] init];
[self addSubview:rowOfThree];
for some reason it doesn't add the view.
i tried defining the labels in "RowOfThree" both in IB and programmatically and it still didn't work.
Thank you.
Typically, a UIView (and the subclasses) are initialized using initWithFrame:. Maybe you did that in your own implementation of init, I don't know, but it may very well be that your view has a frame of {0,0,0,0} and therefore 0 height and 0 width. Set the frame by hand and tell us whether this works.
CGRect newFrame = CGRectMake(0.f, 0.f, 200.f, 40.f);
RowOfThree *rowOfThree = [[RowOfThree alloc] initWithFrame:newFrame];
[self addSubview:rowOfThree];
[rowOfThree release];
SanHalo's answer is most likely correct but in addition, if you're using Interface Builder, you should not be directly initializing views that are defined in the nib. If you do, you have to use initFromNib instead of just init.