Using AutoLayout in Xcode disables my code from working (dynamically)? - iphone

In my project I have enabled AutoLayout in order to make the app scale properly in both iPhone 4 and iPhone 5. Everything worked fine with doing so, but I found a new problem which I am not sure how to handle.
In my project I have a normal method which checks if a boolean is yes or no, if yes the interface should add a button into the interface and keep my tableView in its current state. But if the method returns no, the button should disappear and make the tableView's height higher! Everything with the button works fine but for some reason after start using AutoLayout my code for increasing the tableView's height stopped working (which worked before).
Now what can I do to make my tableView's height increase in height even if I am using AutoLayout? Here is my code:
- (void)viewWillAppear:(BOOL)animated
{
NSString *bookName = [self getCurrentBookName];
if([self isBlank:bookName])
{
[self.currentBookLabel setText:NSLocalizedString(#"LabelNoBookChosen", nil)];
}
else
{
[self.currentBookLabel setText:[self getCurrentBookName]];
}
[super viewWillAppear:animated];
if([self isAppLicensed] != YES) <------------------THIS IS WHERE I CHECK THE BOOLEAN METHOD!
{
[actionAppStore setHidden:TRUE];
CGRect framez = [tableView frame]; <------------------ THIS CODE IS NOT WORKING ANY LONGER!
[tableView setFrame:CGRectMake(framez.origin.x, framez.origin.y, framez.size.width, framez.size.height+77)];
}
else
{
[actionAppStore setHidden:FALSE];
}
}

You can either
Turn off autolayout and use the autosizing masks. The non-autolayout autosizing masks give you the same control over having controls increase or decrease their size for the 3.5" screen vs the 4" screen. This is probably the easiest solution. And it gives you compatibility with iOS 5 devices, too.
If you want to use autolayout, then you should create an IBOutlet for the appropriate constraint, and then programmatically change that.
On that latter example, consider a layout where I have a table view and a control at the bottom for the app store (I'm inferring this from your variable names). There are two ways to hide and show that bottom control. One is to hide it (or set its alpha to zero or to removeFromSuperview), remove the unnecessary constraints, and recreate the new appropriate constraints.
That works, but it's a hassle. I now prefer to either change the height of what I want hidden to zero, or, if it's already on the edge of the screen, I'll just slide it off the edge so you can't see it anymore.
Thus I might visually hide the app store control, not by playing around with it's hidden property, but rather by changing its height to zero (or, to show it, to 77). That way, the other constraints will automatically resize the other controls. Thus I hide it with:
self.appStoreHeightConstraint.constant = 0.0;
[self.view layoutIfNeeded];
And I show with:
self.appStoreHeightConstraint.constant = 77.0;
[self.view layoutIfNeeded];
I do that with an IBOutlet called appStoreHeightConstraint which is linked to the height constraint for that bottom control.
Alternatively (and only if the item being hidden is at the bottom), I can slide the it off the bottom of the screen (this time with a IBOutlet on the bottom constraint), with:
self.appStoreBottomConstraint.constant = 77.0;
[self.view layoutIfNeeded];
and show it with
self.appStoreBottomConstraint.constant = 0.0;
[self.view layoutIfNeeded];
In the interest of full disclosure, I should mention that you have to be very careful about designing the constraints so that they simultaneously (a) minimally describe your layout; but (b) fully describe your layout. You want to avoid the horrid conflicting or unsatisfactory constraint messages.
Focusing on the vertical dimension only, that means that have the following constraints:
table view's top constraint was to the top of the superview;
table view's bottom constraint was to the top of the app store view;
table view should have no height constraint (because that's what you want to change as other stuff changes);
the app store control at the bottom had it's top constraint linked to the tableview;
the app store's bottom constraint linked to the bottom of the superview; and
the app store's height constraint fixed at 77.0.
But I find that IB always (in a good faith effort to ensure that the constraints are unambiguous) is trying to add additional constraints (e.g. the height of the tableview). So, I personally wrestle with IB to get the constraints quite right (usually I end up lowering the priority of the table view height and when I'm all done with everything else, I can go back and get rid of the table view height).
Maybe I'm making too much of the hassles in editing the constraints, but I mention it because if you don't get them all exactly right, efforts to effect UI changes by altering one constraint will not yield the results you want. Once you get the hang of it, it's pretty easy and it's a thing of beauty to see everything adjust accordingly (esp on complicated scenes). But it can be a hassle to do those first few times.

You probably need to tell the view to layout its subviews using layoutIfNeeded.
See my question I just solved and this question is also related.

Related

iOS7 - View under status bar - edgesForExtendedLayout not working

I have a project that was built last year, and it uses XIBs, no storyboards. The XIBs do not use Auto Layout, but they do use some Autosizing. I have an issue when running with iOS7, in which all the views are tucked under the status bar. I fully understand this is a new feature with iOS7, in which this can be expected. However, all of the solutions for fixing it to not do this are not working. I have an image at the top of the view that always shows under the status-bar, and I'm not using nav-bars or anything like that.
I have tried updating the Y-deltas in the XIB (they have no effect on the view), I have tried setting the edgesForExtendedLayout to UIRectEdgeNone (does nothing), and a multitude of other things. Every time, the status bar shows with the view tucked under it, no matter what I do.. that is unless I manually move down the view in the XIB to allow room for the status bar (but that solution doesn't work because it doesn't look right in iOS6, of course).
What's odd is that even when I try a line of code to hack in a view-shift, it doesn't work (like the following):
self.view.frame = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y+20, self.view.frame.size.width, self.view.frame.size.height);
..Not that I would go with that kind of solution, but it's just odd that it didn't work (the only time I typically see that not work is if Auto Layout is in place, which it's not in this case).
It is a design requirement that the status-bar shows, and I'm just stumped on why I can't set the view to be under the status bar for iOS7. I have read every single Stack Overflow post on the subject, as well as Apple's transition/guides. Once again, to reiterate, I fully understand how it should function and what the expected solution should be to this, but none of that seems to be working for this particular project.
I am an experienced iOS dev, but this project was built by another team, so I don't know if there's something hidden somewhere in the XIB files, plist, or code that could be trumping the above settings. Please let me know if there is something else that can be looked at on this, or more information I can provide.
Thanks in advance!
If you set the iOS 6/7 delta values in Interface Builder, remember to set "View as" to "iOS 6" on the Interface Builder Document, since it is the iOS 6 layout you want to replicate. The deltas will then be used only on iOS 7 to push the content below the status bar. If you leave "View as" set to iOS 7 (the default) the deltas will instead give you the iOS 7 look on iOS 6.
However, the deltas will not help you if you reposition or resize views programmatically based on the view frame, since the frame does not account for the deltas.
Instead of using the deltas, the best solution I have found is to enable Auto Layout on your main XIB and then set the top space constraint on your top/content view to follow the Top Layout Guide. This guide was introduced in iOS 7 and represents the position below the status bar. Unfortunately the guide is not available in Interface Builder when not using Storyboards, but you can add it programmatically.
What I did was add a top space constraint to the superview instead in Interface Builder, and created an outlet for this in the code. Then, in viewDidLoad, if the topLayoutGuide is available (iOS 7+), replace the constraint in this outlet with a version using the Top Layout Guide instead.
if ([self respondsToSelector:#selector(topLayoutGuide)]) {
[self.view removeConstraint:self.containerTopSpaceConstraint];
self.containerTopSpaceConstraint =
[NSLayoutConstraint constraintWithItem:self.contentView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.topLayoutGuide
attribute:NSLayoutAttributeBottom
multiplier:1
constant:0];
[self.view addConstraint:self.containerTopSpaceConstraint];
[self.view setNeedsUpdateConstraints];
[self.view layoutIfNeeded];
}
For reference, the solution below did work when I applied it to my ViewControllers. However, it's not ideal and a bit hacky. If it's the only approach I can take, then so be it, though.
float systemVersion=[[[UIDevice currentDevice] systemVersion] floatValue];
if(systemVersion>=7.0f)
{
CGRect tempRect;
for(UIView *sub in [[self view] subviews])
{
tempRect = [sub frame];
tempRect.origin.y += 20.0f; //Height of status bar
[sub setFrame:tempRect];
}
}
Apple are pushing you to use autolayout to accomplish this. You need to set a constraint to the "Top Layout Guide" from the top subview in your view.
See this document for examples:
https://developer.apple.com/library/ios/qa/qa1797/_index.html
To do this without XIBs, you'll need to add the constraint programatically. Apple's docs give a good example of this, which I've summarised below.
Giving that the topLayoutGuide is a property on a view controller, you just use it in your dictionary of variable bindings. Then you set up your constraint like normal:
id topGuide = [myViewController topLayoutGuide];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(button, topGuide);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[topGuide]-20-[button]" options:0 metrics:nil views:viewsDictionary];
The documentation for this can be found in the UIViewController class reference.
1)The simplest solution if you don't mind having an opaque navigation bar:
self.navigationController.navigationBar.translucent = NO;
2) svguerin3's answer can't work in the general case. For example, if one of your subviews uses autosizing to be hooked at the bottom of its container, then its new position will be wrong. And it could go out of screen in the worst case.
- (void)viewDidAppear:(BOOL)animated {
[self.view setFrame:CGRectMake(0, 20, self.view.frame.size.width, self.view.frame.size.height)];
// OR
self.view.transform = CGAffineTransformMakeTranslation(0, 20);
}
Have you tried viewing your XIBs as source and removing any line containing edgesforextendedlayout ??
We had to remove this line in our storyboard's scenes since our storyboard's scenes' main views are represented by XIBs
What was happening for us was that somehow, in some scenes, the XIB content for the scene's main view was being pushed down by the height of the status bar and the navigation bar.
Removing that line allowed the XIBs to be displayed as if their top originated at the same top of its storyboard's scene.
Sadly, we have no idea what triggered this, but I saw it happen when changing the order of the contents within the XIB's main view so that a UITextView appeared first. Rearranging the order of items after this was triggered had no effect in removing this unwanted behaviour.
Hope this helps anyone else running into this type of problem.
If you are using the storyboard, after setting the top layout of your view, you can uncheck the "Under Opaque Bars" in the Attributes Inspector

how to set the content size of a scrollView Dynamically

I have a scrollView and on that scrollView, i have many labels and textView along with one tableView. scrollView is working perfectly fine when the no. of rows are less in tablView but if tableView have more number of rows then i am not able to full content and even i am loosing lots of information in tableView because the tableView isn't scrollable. i have added all the lables, textView and tableView as a subview of scrollview. Can anyone help me in that so i can get full tableView and make it proper scrollable.
Here is my code to set the content size of scrollView.
float maxHeight = 0;
for(UIView *v in [self.scrollView subviews]){
if(v.frame.origin.x + v.frame.size.height > maxHeight)
maxHeight = v.frame.origin.x + v.frame.size.height;
}
self.scrollView.contentSize = CGSizeMake(_scrollView.frame.size.width, maxHeight+100);
First you'll need to resize your table, so that it fits all the rows (since as you said, it's not scrollable). I believe that doing so will make all your rows to not be reusable, so if it's a big table you may get performance issues. Also be aware of #David H's answer, so if you really want to do this, you might be better of using another UIScrollView instead of the UITableView (it won't bring much benefit either way).
Second, you'll need to move all the views beneath the tableview, so that they won't overlap. An easy way to do this is to have those views put together in a different view, so that you only need to move one view instead (in this case however, you'll also need to resize that view accordingly).
Finally, instead of looping through all the scrollview's subviews, it should be easy enough to keep track of the view that is closest to the bottom (this depends on how you generate and add the views to the scroll view).
A tableview is a scroll view subclass, and generally Apple says adding it to another scroll view will cause problems. That said there are ways to make it work. Search here or on google for terms like "how to put a uitableview in a uiscrollview".
EDIT: to make it not scrollable you could put a transparent view over the table view that stops or eats touch events.
self.scrollingView.contentSize=CGSizeMake(320,372);
Try this.

Organizing NSLayoutContraints between code and IB

I'm sure that this must be a common issue. I have a UIViewController which has a UINavigation bar, and a ContentView (which contains UIView and a bunch of children controls. The user can hide this content view by swiping left, then it animates off the left side of the screen. I want the navigation bar to expand and fill the entire width. You cannot link these two with Interface Builder, so it must be done in code. No problem:
// Called from viewWillAppear
-(void)addLayoutConstraints{
UINavigationBar *navigationBar = self.navigationBar;
UIView *facetView = self.facetViewController.view;
NSDictionary *views = NSDictionaryOfVariableBindings(navigationBar, facetView);
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-[facetView]-[navigationBar]-|"
options:NSLayoutFormatAlignAllBaseline
metrics:nil
views:views];
[self.view addConstraints:constraints];
}
That code snipped should do it just fine. The problem is that I have laid out the UINavigationBar in IB, so that I can easily add customized buttons to it and see it, etc... The issue is that when I layed out the navigation bar in IB, IB adds a bunch of default constraints that cannot be removed. One of which is in conflict with the one I added in the code above.
How do you get around this? Here are 3 possibilities I came up with:
1.) I could create the UINavigation bar programmatically as well. This way it only has the constraints that I add to it. I'd prefer not do it this way, but it's an option
2.) Using IB, somehow delete the default constraints so that they don't' collide. I do not see a way to do this
3.) In code, remove the default constraints that IB adds to the UINavigation bar, then add my custom constraints. I do see that there is removeConstraint and removeConstraints. I suppose I could retrieve the constraints, remove them, then add my own. Again, kinda long messy process.
What is your opinion? Have you done similar?
Option 3 is the supposed way. It is exactly talked about in this WWDC 2012 session — Introduction to Auto Layout for iOS and OS X.

iOS - resize UITableView height programmatically

I'm having a problem trying to programmatically resize the height of a UITableView hosted within a UIViewController, using iOS5 and Storyboards. The VC displays a master/detail record style with the UITableView displaying the detail records. Depending on the type of master record shown, a set of buttons may be needed at the foot of the screen. If the buttons are not needed then I want the UITableView to extend it's height to take advantage of the space available. I'm using the following code :
CGRect tableFrame = [tableListView frame];
if (blnApprovalRec == YES)
tableFrame.size.height = 127;
else
tableFrame.size.height = 170;
[tableListView setFrame:tableFrame];
This code is called whenever the master record changes, including when the screen first loads in viewDidLoad. The problem is that when the VC loads, the UITableView doesn't paint using the size specified - it just paints with the default size from IB. Once the user changes the master record so the table is reloaded then everything works fine and the size changes as required. I've tried forcing a repaint using setNeedsDisplay, setNeedsLayout and reloadData but none of these worked.
The problem is that when the VC loads, the UITableView doesn't paint using the size specified
This happens, when table view is loaded, but it's UI is not getting refreshed. Please verify if you have forcefully called in viewDidLoad or viewWillAppear.
Hopefully your this code is in seperate method:
CGRect tableFrame = [tableListView frame];
if (blnApprovalRec == YES)
tableFrame.size.height = 127;
else
tableFrame.size.height = 170;
[tableListView setFrame:tableFrame];
When the view appear initially, you may have the default selected value from master record.
You can set that value/instance in calling function in viewWillAppear.
Can you show method name and code for, how you are calling above snippets of code forcefully?
You can resize UITableView programmatically but you need to create UITableView programmatically too. Don't use Storyboard.
I had the same issue, just needed to move the code from
- (void) viewDidLoad
to
- (void)viewDidAppear:(BOOL)animated
This is tricky, its hard to resize things like this dynamically.
i would try "setNeedsLayout" And "setNeedsDisplay" for your table, and your screen to force a redraw.
Other than that, I would storyboard it to the minimum size and use code to expand it.
Generally apple doesn't like us doing this, your buttons should be drawn over the top of the view inside another view, if thats possible.
Sorry I can't be more precise but I have solved all these issues by mucking around and with hacks, and giving up on resizing things and doing re-designs. Please let me know how you go though :)
Make sure you are setting the frame after the table has loaded.
Which method do you call that code in?

Adding a dynamic-height UITableView into a scrolling view?

Hello all – I'm getting into iPhone development and have hit my first confusing UI point. Here's the situation:
My app is tab-based, and the view that I'm confused about has a static featured content image at the top, then a dynamic list below into which X headlines are loaded. My goal is to have the height of the headline table grow as elements are added to it, and then to have the whole view scroll (both featured image on top and headline list below). So, I guess my question comes in two parts:
1) First, how do you set up a dynamic-height table view that will grow as cells are added to it. So far I've only been able to have my tables handle their own scrolling.
2) Then, what is the root NIB view that the featured image and the table should live in to enabled scrolling? I've dropped oversized content into a UIScrollView now, although did seem to have any success with having it automatically scroll.
Thanks in advance for any help on this subject!
To the first:
As i understand your situation:
You want to add a image to the top of the UITableView and the image should scroll with the UITableView, shouldn't?
The UITabeView has a property called tableHeaderView. It's just a view, so you can set a UIImageView to it.
(I have no xCode at the current time, you need to edit the code)
UIImage *image = [UIImage imageNamed:#"myCoolPic.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame =CGRectMake(0,0,width,height);
tableView.tableHeaderView = imageView;
[imageView release];
What you're asking is probably doable with Interface Builder (or not, I don't know) but I know the code way to do it.
To change the height of the table all you do is set the frame of the UITableView object. The default height of a UITableViewCell is 44 I believe, so set it to multiples of that depending on how many cells you have. Of course your cells can be any height so you will need to keep track of what you report in heightForRowAtIndexPath and set the table frame accordingly.
UITableView will certainly live in a UIScrollView and both components can scroll. The table view needs to become a subview of the scroll view, so does the image. Then you will scroll the table if you drag on it directly or scroll the scroll view if you drag the image or the scroll view.
For the first question, I'm a little confused by the way you ask it: "how do you set up a dynamic-height table view that will grow as cells are added to it." Table views have a function that it calls before the table is fully loaded with data called "numberOfRowsInSection." So the number of cells is based on that function, and should you update the variable used to determine the return value of that function (usually [myArray count]) it should automatically find the right size for the whole table.
However, variable height cells are something that I found kinda tricky and I've solved it using the following:
There are some UIKit NSString additions that you might find useful.
http://developer.apple.com/iphone/library/documentation/uikit/reference/NSString_UIKit_Additions/Reference/Reference.html
Particularly the sizeWithFont: functions.
Table views also have a 'heightForRowAtIndexPath:' function that is called 'numberOfRowsInSection' amount of times. Each call determines the height of the cell at the indexpath.
So, for example: (assuming myArray is an array of NSStrings)
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return [[myArray objectAtIndex:indexPath.row] sizeWithFont:myFont];}
This will return a height based off of your actual data, piece by piece. There are other functions to specify how the text wraps and truncates, etc. as well.
It doesn't feel like a great solution because you end up fetching your data twice, once to determine the height, and then again when you configure the cell in 'cellForRowAtIndexPath:' However, it does work!
I've learned a lot in the past few weeks and have gone through a few iterations of addressing this problem. My first solution was to manually measure the table height, then set the table rect to display at that height, and finally to set the scrollView's content rect to encompass the the table and top feature. What that solution did basically work, I started encountering some display issues when branching out into new views with different toolbar configurations. It seemed that my manual frame size was interfering with iPhone's native content scaling.
So, I scrapped the manual sizing and went to just making that top feature block be a custom table cell that displayed within its own section at the top of the table. I made a hard logic definition that section 0 only had one table cell, and that cell was my custom layout that I linked in through Interface Builder. I was then able to get rid of ALL my messy custom scaling logic, and the whole system is cleaner, smoother, and works reliably.