UIScrollView Frame Always CGRectZero - ios

I have a nib that has the standard UIView and I've also included a UIScrollView as an IBOutlet (yes, it is hooked up), not as a subview of the main UIView, but just out on its own. Autolayout is turned off. The scroll view has several subviews and is larger than the main view. In viewWillAppear:, I set the content size of the UIScrollView to its current frame size, and then set its frame to the size of the main view, and add it as a subview of the main view.
Unfortunately, nothing is showing up. When I NSLog the frame of my UIScrollView, it is coming back as {0, 0, 0, 0} (CGRectZero). I thought this was odd, so I went back and tried logging the frame before I do any changes to it. Still zeroes. Logged it out in viewDidLoad before anything is done to any of my view elements. Still zeroes. (FWIW in my nib, the frame is {0, 0, 320, 896})
I've had this issue with several of my controllers, but it seems to be hit or miss. Sometimes it works, other times I get the empty frame. Typically, recreating everything from scratch seems to fix the issue, but I don't know why, as I'm setting everything up the same both times.
Running Xcode version 6.1 (6A1052d), iOS SDK 8.1 with a deployment target of 7.0
Let me know if there is any other relevant information I can give that might help.
EDIT 1: To address the questions about my UIScrollView being a "subview", here is what my view heirarchy looks like in the document outline:
As you can see, the UIScrollView is a "subview" of the view controller, but is not a subview of the "main" UIView which has the controller's view outlet.
EDIT 2: More images and some code. Here is a better look at how my nib is set up:
I add my scrollView to the main UIView as follows:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
scrollView.contentSize = scrollView.frame.size;
scrollView setFrame:CGRectMake(0, 100, self.view.frame.size.width, self.view.frame.size.height - 100); // I slightly modified this because there are other variables that determine its Y position and height
[self.view addSubview:scrollView]
}
But I still don't think any of that code matters- for whatever reason, my UIScrollView is coming back nil. If I have the view created in my nib, the outlet is connected properly, how would the view still be nil? I'm creating my controller via initWithNibName I've tried cleaning the project, and removing the app and reinstalling.

Best guess on an answer, and some comments:
Nibs are simply serialised object trees. You can have as many view as you want as root. The problem (big one!) is that if they are not "connected" to outlets that retain them, at some point you will loose those references.
Since your UIScrollView is not a subview of the main view controller view on the Nib, it's not retained by it (views retain their subviews). So, it's up to you to retain it on your view controller.
My guess is that at the point you are trying to set the frame in the View Controller, the scroll view is already gone. I'm not sure how sure when are you doing it, but it might be after some run loops, so the unretained scroll view is dealloced.
Workarounds:
Instead of a variable for the IBOutlet, use a strong property (#property (strong, nonatomic) IBOutlet UIScrollView *scrollView).
Add the __strong qualifier to the variable.
Add the UIScrollView as a subview within the nib, so when the object tree is deserialised it's already retained by its parent.
Given that you purposefully put the UIScrollView outside of the main view hierarchy, I'm assuming you don't want option 3, so I'll just go with 1.

First, you might want to consider using autolayout. It makes things a lot easier once you make the investment to understand how it works. Check out this Apple doc on using UIScrollview with autolayout.
If that's not an option, the problem is that you're setting up your scrollView at the wrong point in the cycle. The scrollView needs a chance to lay itself out. By the time you hit viewWillAppear, you've missed sub view layout.
So, you could try:
Configure your scrollView in willLayoutSubviews and set its setNeedsLayout property.
Explicitly call layoutIfNeeded on your scrollView in viewWillAppear. The idea is to force another subview layout cycle. But, I'm not 100% sure it would work -- it might be too late at that point.
Note that the size of the main view (self.view) isn't determined until viewDidAppear. It's not always correct in viewWillAppear. So, you have a problem in your setup: you can't really set up your scrollView until you know your view size, but you don't know that until it's too late! You might want to redesign to avoid that dependency.

Related

Why viewDidLayoutSubviews is called twice only on initialize?

I am going to use it by adding a child VC. (Coding with code)
ChildVC is using autolayout (snapkit).
At initialization, viewDidLayoutSubviews is called twice.
At the first call, the childVC.View is set to full screen Frame.
In the second call, it is set according to the autolayout setting.
I found out that if the child VC's Frame is not set, it is set to full screen.
(Resize View From NIB)
I want to know how to turn off Resize View From NIB programmatically.
The problem is, I want to know why viewDidLayoutSubviews is called twice.
I'm curious as to why the childVC.View size changes when the second call is made.
I want to know one more thing. Isn't this usually used when setting subView's witdh and Height?
someView.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
make.width.equalTo(view.bounds.width)
make.height.equalTo(view.bounds.height * 0.58)
}
Based on VC.View bounds ratio?

Xcode Interface Builder Height/Width Settings vs. View Controller

I'm having a hard time wrapping my head around how frame properties (height, width, posX, posY) work in terms of setting them in a View Controller vs on the Storyboard (Interface Builder).
For instance, let's say I have a UICollectionView object that I set to have a width of 400 and a height of 800. Then, in my code, I set the frame of that same object to 600 x 400. I haven't really found a consistent behavior. I tried setting the frame in viewDidLayoutSubviews and it sort of worked - but it seem to 'jump' back and forth between that and what was set on the storyboard.
Basically my question is, when do the properties on the storyboard change the UI object? I assume that I just need to know that, and then reset them in the View Controller after the fact. Or, is there a way to set the height and width empty so that I can do it all in the code?
Any insight into this would be very helpful!
If you use AutoLayout in your project then setting frames of the view objects you configured in storyboard won't work. Because after you set the frames, AutoLayout will update frames again which makes the frames set by you not working. If you want detail, you can check this article:Advanced Auto Layout Tools But you can set frames of view objects created programmatically to position them.
You can check if you have turned on AutoLayout in you storyboard file's file inspector. There is one thing though, if you do want to use AutoLayout, be sure to not set view's translatesAutoresizingMaskIntoConstraints to NO. The default value of this property is YES. If you use AutoLayout, this property is set by storyboard for you, because constraints created in storyboard are enough to layout the views.
EDIT:
1) if you are not using AutoLayout then setting frames in code should work as expected.
2) yes you can, a little tricky though. you must create UICollectionView yourself using [[UICollectionView alloc] init] or load it from nib. and then configure cell in IB with a xib file. you can use AutoLayout to layout subviews of cell in xib file. and register the class of cell to UICollectionView or load cell object from nib yourself. then you should calculate the size of every cell and let AutoLayout layout subviews of cells.
although this is easier than layout interface entirely in code, it's still a little complicated. the better way is using AutoLayout. Since not all the layout detail can be done in the design time since some views' frame may be different depending on data. you can make a basic layout with AutoLayout first, then IBOutlet the constraints you want to configure on the fly. and change the constant property of constraint objects later. this way, you can 100% control the layout process and also let AutoLayout do the dirty jobs you don't want to do yourself. I suggest you read official docs of AutoLayout and other good resources about it. The learning curve is steep at first, it may make you want to kill yourself too. But it's really powerful and easy to use. once you figured out how AutoLayout works, it will make your iOS development life much easier.
If you want to set the size of things through code, you could try creating outlets from the storyboard to the View Controller. Then in the View Controller, you can use viewDidLoad or viewDidAppear to set the size properties of your object(s).
viewDidLoad will get called when that view is first created. viewDidAppear will get called each time that view comes back onto the screen (like if you are going back with a navigation controller).

View height problems (continued)

This is in continuation to the problem I had here(which is still unresolved): link
But this may help understand what is the problem.
I created just a simple test project ('Empty Application') and added a view controller with a XIB file (check box: 'With XIB file for user interface' selected). Code looks like this:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"didLoad: %#",NSStringFromCGRect(self.view.bounds));
// Do any additional setup after loading the view from its nib.
}
-(void) viewDidAppear:(BOOL)animated
{
NSLog(#"didAppear: %#",NSStringFromCGRect(self.view.bounds));
}
This is the output:
2013-07-26 17:05:28.502 testtest[5926:c07] didLoad: {{0, 0}, {320, 548}}
2013-07-26 17:05:28.506 testtest[5926:c07] didAppear: {{0, 0}, {320, 460}}
How come they are different?
(ps. I am testing on 6.1 simulator)
When the viewDidLoad method is called, your view controller has only just been loaded from your storyboard or XIB, and so the view dimensions are equal to those that you have in the XIB (those looks like iPhone 5 height dimensions).
Later, when viewDidAppear: is called, the view has already appeared on the screen, so it has been resized appropriately to actually fit on the screen, so its dimensions may be different to those in your storyboard, and consequently different to those that are set when the view is loaded.
In your case, it looks like your storyboard or XIB file is set to iPhone 5 screen size (548 = 1136/2 - status bar), and you are testing in a pre-iPhone 5 simulator or device with a 480x320 point screen, so the view gets resized down to 460 points high to fit on the screen.
This could have perfect sense.
ViewDidLoad is called lazily when first access of controller.view, so by that time the frame is not set yet. This means that you can not rely on the frame/bounds sizes at this point because it will only contain a default value (although in many cases it will be correct).
In ViewDidAppear, the frame is usually set, although if your parent controller is setting any animation you could also have a temporal frame state instead of the final one, but it is not usual as by convention this method is called when the view is already displayed.
For example, if you are loading the view from an IB file, the frame you will get in the viewDidLoad is the one you have in the IB file, but maybe the final size for your view is smaller/bigger, and then you will get another one in your viewDidAppear.
Instead of that, you should create all your elements resizable (use Spring&Struts, AutoLayout or any other similar alternative) so they will be properly displayed when the frame is set.
When a ViewController presents its view, it normally shrinks that
view so that its frame does not overlap the device’s status bar.
So when you NSLog in viewDidLoad, the View i not yet loaded so ViewController has not shrinked the frame yet but in viewDidAppear , it has done the resizing.
There is a property in UIViewController
wantsFullScreenLayout
Setting this property to YES causes the view controller to size its
view so that it fills the entire screen, including the area under the
status bar. (Of course, for this to happen, the window hosting the
view controller must itself be sized to fill the entire screen,
including the area underneath the status bar.) You would typically set
this property to YES in cases where you have a translucent status bar
and want your view’s content to be visible behind that view.
As far as i know, ViewDidLoad will set the bounds for your application as defined, in the RootViewController/XIB-file for root view, or maybe the AppDelegate.
If you define the applications bounds there (not sure in which one of these), ViewDidLoad functions in the entire app, will initially, set the height according to that.
Once the View is loaded, and ready to 'appear', actual bounds may be requested.
Hence it is advised to request bounds/sizes in the ViewWillAppear/ViewDidAppear methods.
-viewDidLoad is called the first time viewController.view is called. It is called before the view is returned. This has a very important implication. In order for the view to be shown or sized, it needs to be added to the window or some other view.
How does this happen? It looks something like [window addSubview:viewController.view].
So, the fully loaded view is needed before it can be placed into the view hierarchy. This places limitations on self.view when used within -viewDidLoad.
self.view.superview and self.view.window will always be nil.
self.view is not yet sized to fit into self.view.superview.
With this understanding, you can see how self.view cannot know it's final size when -viewDidLoad is called.
UPDATE
If you need to apply a custom layout to a view's subviews, you may want to consider subclassing UIView and using -[UIView layoutSubviews].
Barring that, are you having trouble with -viewWillAppear: instead of -viewDidAppear:? -viewWillAppear: is called before the view is displayed, so you won't have odd visual effects when you resize the view.

UIView subview auto resizing

I wrote a little UIView subclass to show a progress HUD. That HUD view works perfect but I faced a little problem in the last days.
In my application I'm presenting a UIViewController in a custom way. When the user selects a row in a tableView I'm creating an instance of my second viewController, move it to the current VC, set it's view's height to zero, add it as subview of the curren VC's view (at the position of the selected cell) and animate the height back to original.
The behaviour looks pretty cool and works great.
But when the second view is added as subview, I'm adding a HUD to this view. When the second view is resizing to the original height, the HUD sticks to the top of the view and is just a few pixels high:
I played around a bit with NSLayoutConstraints... But I didn't get it working until now...
Has someone a good idea on that one? Or does anybody know good and well explained resources on these constraints?
The HUD is actually a background view with the little window as subview. All other elements (the progress view, labels and so on) are subviews of the little window.
In terms of good resources, I definitely recommend WWDC2012's 3 videos: Introduction to Auto Layouts for OSX/iOS, Auto Layout by Example, and Best Practises for Mastering Auto Layout. These have some tips for looking at ambiguity in the layout and dealing with conflicts
Another great reference is Erica Sadun's iOS6 recipe book.
Re your problem. I'm assuming that you're not seeing an error message and you're laying out the progress HUD entirely in the XIB. If so, it sounds like you have two constraints that aren't behaving as you'd like from the xib - the height from the top of the superView and the height of the HUD
Firstly, create an outlet for the constraints to the .h file
#property (strong) IBOutlet NSLayoutConstraint *HUDSuperViewToHUDConstraint;
#property (strong) IBOutlet NSLayoutConstraint *HUDHeight;
Next, in the method in which you open the new viewController with the HUD remove the constraints so that there is no conflict when you first show the new view
[HUDSuperView removeConstraint:self.HUDSuperViewToHUDViewConstraint];
[HUDSuperView removeConstraint:self.HUDHeight];
After you've called [HUDSuperView layoutIfNeeded] for the first time, in the animation or wherever, add the constraints and call layoutIfNeeded again
[HUDSuperView addConstraint:self.HUDSuperViewToHUDViewConstraint];
[HUDSuperView addConstraint:self.HUDHeight];
[HUDSuperView layoutIfNeeded];
If you call these within an animateWithDuration it may even animate the appearance...cheesey
Hope this helps - it's probably more of a step toward the solution rather than the solution itself. Recommend minutes 17 and 53 in the AutoLayout by Example video too.
Steve

Accessing parent view when adding subview

I have a ViewController that has 2 NSViews in it of different sizes. I'm wanting to add the view of a custom ViewController as a subView to both of these NSViews and have it dynamically size to fill (the 2 parent views). I can accomplish this just fine in the implementation file for the main layout, but its a lot of code. I would like to instead have my custom ViewController do all the work. To do that, I need to know the height and width of the view that I am adding my custom view to. There is a parentViewController property, but it isn't doing anything for me. Is there a way to reference the view that a view is being added to?
In my custom ViewController viewDidLoad method I would like to be able to have
[self.view setFrame:CGRectMake(0, 0,
self.parentViewController.view.frame.size.width,
self.parentViewController.view.frame.size.height)];
but the height and width are both nil here.
I've been digging through documentation for hours and I am still confused. Any help would be much appreciated.
The parentViewController property should be set in the first place. It will be nil if it is not set. You have not specified if it is set correctly. There is a simpler way to do this though, assuming that your view's superview is set correctly. Try this:
[self.view setFrame:self.superview.frame];
or make the rect from the superview's frame if you need to alter something in there.

Resources