Getting subViews' frames in Storyboard - ios

I just want (for now) to get the dimensions of a subview on the view controller's instantiation.
[This is a reduction to as simple a case I can find of a previous question. I am trying to figure out why subViews of scenes in Storyboards are not behaving the way I expect, which is to say: like XIBs do - I just want to get dimensions of my subviews before anything is actually drawn to the screen]
To condense the problem down to a new, clean project, I do this:
create a new, single view project with "using Storyboard" checked
add a single UIView to the default existing MainStoryboard_iPad.storyboard (and change its background to green to make it more easily seen - beyond shrinking the dimensions some, this is the only change I make from the default UIView I drag onto the scene)
option-click the ViewController.h file in the navigator to bring it up in its own frame underneath the Storyboard frame and insert a pair of braces underneath the #interface directive
control-click-and-drag from the UIView in the Storyboard to ViewController.h and tell it to name the outlet firstViewFirstSubView
So we now have for ViewController.h:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
{
IBOutlet UIView *firstViewFirstSubView;
}
#end
Then, I add this method to the ViewController.m:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(#"View Controller will appear. firstViewFirstSubView: %# ", firstViewFirstSubView);
NSLog(#"subView's dimmensions: %f by %f at %f, %f", firstViewFirstSubView.frame.size.width,
firstViewFirstSubView.frame.size.height,
firstViewFirstSubView.frame.origin.x,
firstViewFirstSubView.frame.origin.y);
}
At this point, I expect to be able to get the dimensions of the UIView subview. I get all 0s, though:
2012-11-15 15:21:00.743 StoryboardViewBounds[11132:c07] View Controller will appear. firstViewFirstSubView: <UIView: 0x9379730; frame = (0 0; 0 0); autoresize = TM+BM; layer = <CALayer: 0x9378e40>>
2012-11-15 15:21:00.744 StoryboardViewBounds[11132:c07] subView's dimmensions: 0.000000 by 0.000000 at 0.000000, 0.000000
What am I doing wrong? It seems like this should be very straightforward, so I think I must be missing something simple, whether throwing the right switch in the Storyboard editor or implementing a method that Storyboard needs.

Those dimensions are calculated and set when the call to layoutSubviews is made, which occurs after viewWillAppear. After layoutSubviews is called on the UIVIew you can get the dimensions.
Look at implementing this method in your view controller: viewDidLayoutSubviews. At this point you should be able to get dimensions of your subviews. Note that this call is available starting in iOS 5.0 but since you reference storyboards I assume you are working at or above that anyway.
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
NSLog(#"View Controller did layout subviews. firstViewFirstSubView: %# ", firstViewFirstSubView);
NSLog(#"subView's dimmensions: %f by %f at %f, %f", firstViewFirstSubView.frame.size.width,
firstViewFirstSubView.frame.size.height,
firstViewFirstSubView.frame.origin.x,
firstViewFirstSubView.frame.origin.y);
}

There is a Related issue that I am compelled to address, which hopefully will save someone else a day of debugging:
I am finding out that in Storyboard:
segue-push does not cause subViews to be laid out at -viewDidLayoutSubviews (they are instead laid out at some other time just before -viewDidAppear).
Whereas...
segue-modal and [navController.storyboard presentViewController:] does cause subViews to be laid out at -viewDidLayoutSubviews.
The solution is to put [self.mySubView layoutSubviews] within the viewController's -viewDidLayoutSubviews method in order to manually load the subViews within mySubView.
My case was that I had a custom gradient button that was not properly initializing it's visual appearance.
The button was contained within a scrollView that contained a CustomView which contained the custom gradient button.
So, basically... a button within a view within a scrollView.
The app starts out with a UINavigationController having some other ViewController1 loaded.
ViewController1 contains a button which, when pressed, launches a storyboard segue-push to ViewController2.
(this was arranged in storyboard by control-dragging from the button in ViewController1 to ViewController2).
ViewController2 contains the scrollview/customView/customButton.
In ViewController2's -viewDidLayoutSubviews, I initialize the customButton which is a custom Gradient Button having it's own .h/.m files.
GradientButton.m has an initLayers method which configures it graphically and requires the bounds/frame property of the button to be initialized.
However...
in ViewController2's -viewDidLayoutSubviews, self.customButton had a frame of 0,0,0,0.
A few notes:
Yes, I am calling [super viewDidLayoutSubviews] at the beginning of -viewDidLayoutSubviews.
The only view that has been laid out at -viewDidLayoutSubviews is self.view (ViewController2's initial view, as connected in Storyboard's connections panel. in my case - self.view is a scrollView).
To resolve:
in ViewController2, I created an outlet self.bottomView for the view that contained self.customButton.
in ViewController2's -viewDidLayoutSubviews, I call [self.bottomView layoutSubviews]
This will then cause customButton's frame/bounds to be properly set.
Next, I call [self.customButton initLayers] which now properly initializes my custom gradient button.
A few notes about ViewController2's -viewDidLayoutSubviews: calling [self.view layoutSubviews] causes bottomView's frame to be initialized, but NOT customButton's frame.
In terms of a view hierarchy, -layoutSubviews applies only to the subViews of self.view and not to any subViews of those subViews.
This only appears to be the case with storyboard segue-push.
The storyboard segue-modal and the programmatic [navController presentViewController] both seem to correctly initialize all levels of the view hierarchy (all "subViews of subViews") by the time -viewDidLayoutSubviews is called.

Related

Equivalent to ContentInset for UIViewController?

I'm trying to implement a function where there will be a somewhat permanent banner on top of my UITabBar from my TabBarController. To avoid ruining the content in the viewController.view I would like to use some kind of content-inset.
If all the viewControllers of the tabBarController were tableViews or scrollViews, then I could change their contentInset, but not all of them are.
I have tried changing the viewController.view.frame to have a shorter height (to make room for a 44px banner below view and above tabBar), but the view won't update. I'm guessing it doesn't like the idea of the 44px 'void' in the viewController itself (as nothing is below the UIView).
I have thought of a few different ways:
Somehow changing the height/origin of the actual TabBar without changing the design, making 44px extra room at the top of the tabBar, which would hopefully update the 'above' viewController's constraints
Somehow edit the viewController.view's constraints to give space between the TabBar and the bottom of the view.
Somehow change the BottomLayoutGuide programmatically
Are any of these possible? I have searched, without any luck.. Are there any other ways of doing this to a general UIViewController?
To clarify, I do have own classes for each of the ViewControllers in the TabBarController, but I would like to find a way of achieving this without having to change any code of the viewControllers. -> I want to make this change solely from the UITabBarController. (eg. (pseudo) self.viewControllers.view.frame.size.height -= 44; (not actually code, shortened)(which doesn't work))
I would advise you to use bounds property of UIView to do the trick. This is how I would do this:
Step 1 : Add a category on UIViewController to change the view controller's view down by passed in points:
UIViewController.MyAddition.h
#import <Foundation/Foundation.h>
#interface UIViewController (MyAddition)
- (void)moveViewDownBy:(CGFloat)iDownPoints;
#end
UIViewController.MyAddition.m
#import "UIViewController+MyAddition.h"
#implementation UIViewController (MyAddition)
- (void)moveViewDownBy:(CGFloat)iDownPoints {
CGRect bounds = self.view.bounds;
bounds.origin.y -= iDownPoints;
bounds.size.height = -iDownPoints;
self.view.bounds = bounds;
}
#end
Step 2 : Call this category on your ViewController's loadView method:
[self moveViewDownBy:<Height_Of_Your_Top_Banner>];
You can't change height of a viewController's frame.Because it's height/width is readonly
So,do with this tricky
CGRect frame = self.viewControllers.view.frame;
frame.size.height -=44;
self.viewControllers.view.frame = frame;

how to show an overlapping view iOS

I have a view with some UI components and a button on it, upon touch of a button I want to show a half view with some textfields on it overlapping the initial view, the initial view should be visible partly , the overlapping view will cover only half screen from bottom. Is this possible ?
I don't have any code as I am unable to figure out what it needs to be done, as we show any view it covers the entire screen.
Thanks
there are several ways you can do this, here are two:
1) add a popover controller that gets displayed on your button press:
here's some apple documentation on popovers: https://developer.apple.com/Library/ios/documentation/WindowsViews/Conceptual/ViewControllerCatalog/Chapters/Popovers.html
2) add the new view as a subview to your UIViewController
PROGRAMICALLY:
in the viewDidLoad function you can do the following to initialize the halfScreenView
GLfloat topOffset = self.view.frame.size.height/2;
UIView halfScreenView = [[UIView alloc] initWithFrame: CGRectMake(0, topOffset , [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height - topOffset)
[self.view addSubview:halfScreenView];
-more logic might be needed if you support Landscape orientation, you can always re-assign the location of the view with halfScreenView.frame.origin and halfScreenView.frame.size
-initially you can have this view be hidden
halfScreenView.hidden = YES;
-when you click the button it will show the overlaying view:
halfScreenView.hidden = NO;
USING STORYBOARD:
you can also set up your overlaying view in the storyboard if you have one
-drag a UIView into your UIViewController and set it up where you want it to be located
-initialize the view to be hidden by checking the hidden box in the attribute inspector
-add the view as a property to your view
-manage when to show this view with self.halfScreenView.hidden
-this technique allows you to customize what is inside the new view within the storyboard which is nice
FOR BOTH:
-be careful with layers, you don't want your view to show up behind the one you already present. In the storyboard the last thing inserted goes on top. With in the code you can always access/change the views z position with halfScreenView.layer.zPosition (higher z values are on top)
First create a new class subclassing UIViewController called SecondView (or whatever you want), then design the UI the way you want (in its .xib file)
Then go to your main view controller's file and make an IBAction for that button.
In that method, write:
SecondView* second = [[SecondView alloc] initWithFrame:CGRectMake(0, [[UIScreen mainScreen] bounds].size.height/2, height, width);
[self.view addSubview:second.view];
This will add it to the bottom half of the screen. Put your own parameters for its height and width. When you want to dismiss the view, you can do this inside your SecondView class
[self.view removeFromSuperview];
You can deal with the textFields from within the SecondView class and have them communicate with your other view by doing the following in SecondView.h
#property IBOutlet UITextField* textField;
Hope this helps!
Yes, assuming you are using Interface Builder, go ahead and build the overlapping view and hook up all of the IBOutlets and IBActions. Say this view is called myView. Set myView.hidden = YES and myView.enabled = NO. This hides and disables myView so that it isn't even there from the user's perspective. In the appropriate button's IBAction, change the hidden and enabled properties to YES. That will make the view visible and active again.

iOS iAd Integration Issue With UIToolBar

I am trying to add iAd into my project. The view controller that i am trying to iAd into contains a UIWebView. A UIToolBar is also added at the bottom. Above the tool bar, i have dragged a ADBannerView inside my StoryBoard. This is how it looks like:
To show the ads, this is what i have done so far: Added iAd frameWork, created an IBOutLet from AdBannerView named "banner", in viewDidLoad i assigned the delegate to self. Then i added the AdBannerViewDelegate methods and also added the following method:
- (void) viewDidLayoutSubviews {
if (self.banner.bannerLoaded) {
CGRect contentFrame = self.view.bounds;
CGRect bannerFrame = self.banner.frame;
contentFrame.size.height -= self.banner.frame.size.height;
bannerFrame.origin.y = contentFrame.size.height;
self.banner.frame = bannerFrame;
}
}
Well the iAd is properly showing with all the above i have done.
PROBLEM: The first time the view is loaded, it shows the ads properly but when i unload and reload the view again, UIToolBar disappears and is covered by the AdBannerView which is shifted below in place of UIToolBar. Can anyone points out where the problem could be and how to solve it? Thanks.
Your problem is due to setting frames in the viewDidLayoutSubviews method. When using auto layout, you shouldn't set any frames. In the storyboard, when you add the addBannerView, give it a height constraint, and spacing constraints to the to sides of the view, and a vertical spacing constraint to the tool bar (the web view should also have a vertical spacing constraint to the top of the add banner view). Delete the viewDidLayoutSubviews method, and it should work properly.
Sounds like a z-index problem to me, and if this is the case, the solution is quite simple. If you're using Interface Builder, you can use the Document Outline on the left side of your screen to drag and drop views to adjust their z-index. The bottom of the list is the highest z-index and therefore the top on screen view.
Or, if you want to make the adjustments in code, instead of using addSubview:, you can use one of the following.
[<#(UIView *)#> insertSubview:<#(UIView *)#> aboveSubview:<#(UIView *)#>];
[<#(UIView *)#> insertSubview:<#(UIView *)#> atIndex:<#(NSInteger)#>];
[<#(UIView *)#> insertSubview:<#(UIView *)#> belowSubview:<#(UIView *)#>];

How can I animate/manipulate position of UIViews laid out with Storyboards before they render onscreen?

I'm working with a designer to build a simple game for iOS. We've been using storyboards to build the menu scenes.
We have custom view objects, which are composed of multiple subviews (multiple buttons, labels, etc.), which we'd like to animate/slide onscreen after the associated view controller has loaded.
How can I animate the position of these views, which have been built with Storyboards? I can't seem to figure out where in the ViewController lifecycle to implement the animation, as the x/y position specified in the storyboard always seems to override the animation I implement in viewDidLoad, or viewDidAppear, etc.
In a perfect world, my designer would be able to place the view in the desired 'target' position in the storyboard, and I'd then reposition it offscreen in code, and animate it onscreen back to the target position. That'd be easiest from a workflow perspective.
But we'd also settle for positioning the view offscreen on the storyboards, and then simply sliding it onscreen after the page has loaded. So for example, assuming that on the storyboard, viewIWantToAnimate has an initial frame position of (0, -100). And I want to slide it down to (0,0)...
Is there a simple way to do something like:
#interface MyGameViewController : UIViewController
{
__weak IBOutlet UIView *viewIWantToAnimate;
}
#implementation MyGameViewController
- (void)viewDidAppear:(BOOL)animated
{
[UIView animateWithDuration:0.1f
animations:^{
CGRect onScreenFrame = CGRectMake(0,0, viewIWantToAnimate.frame.size.width, viewIWantToAnimate.frame.size.height )
viewIwantToAnimate.frame = onScreenFrame;
}];
}
#end

Can I add a subview inside a subview?

I have added a view inside ViewController using [self.view addSubview:newView]. newView is a UIView type object.
Inside newView I added added a few views using [self addSubview:polyg];.
Polygs also inherit from UIView. But they never get drawn. drawRect:(CGRect)rect is never called.
I'm new to iOS and have no idea what I'm doing wrong.
It probably has something to do with the fact that I did not use self.view when adding views to newView. I can't even use self.view inside newView.
how can I make this work?
edit:
When i create these polyg objects I'm using initWithFrame and sending newView's frame, like this: [[Polyg alloc] initWithFrame:self.frame];
Could this be the cause? Polyg is going to be a movable and resizable polygon, so I figured each should be able to draw on the entire screen.
edit:
I created a method inside newView called touchX:Y that is called from the viewController whenever I touch my finger on the screen. Inside I wrote this: NSLog(#"subview = %i", self.subviews.count);. Whenever I touch the screen I see this on the console: subview = 89 so I'm pretty sure the subviews were added to newView.
I tried adding this to the same touchX:Y method:
for (UIView* subview in self.subviews) {
[subview setNeedsDisplay];
}
But the subviews' drawRect is never called.
The most likely possibility is that newView is either completely off the screen or have a size of (0,0). Right before you add the ployg subviews, use the debugger to print out the frame of newView.

Resources