How to get frame of UIView Nested in UIView's - ObjectiveC - ios

I have an Application in Which there is a lot of UIView's Nested together.
I want to find the frame of some UIView's with respect to Controller.
I have setup an example:
I am able to find to frame of White UIView w.r.t to Controller but how can I find the frame of RedView wrt to Controller.
Code to find the Frame of White View:
- (void)viewDidAppear:(BOOL)animated
{
NSLog(#"--OuterFrame---%#",NSStringFromCGRect(self.grayView.frame));
NSLog(#"--InnerFrame---%#",NSStringFromCGRect(self.whiteView.frame));
NSLog(#"---Frame With resepect to self.view --%#",NSStringFromCGRect([self.grayView convertRect:self.whiteView.frame toView:self.view]));
}
And What if Their is another view inside red View.

You have the right general idea. But it's easier if you use the inner view's bounds, and use self.view to convert:
// assumes innerView is a view somewhere underneath self.view
CGRect innerViewRectRelativeToController =
[self.view convertRect:innerView.bounds fromView:innerView];
This way you only need to specify the view to convert from, and the view to convert to. You don't need to think about what views are in between the two.

Related

How do I get the visible portion of a UIView that is partially panned off the screen?

I have a UIView that I move with a pan gesture to the side of the screen and now the UIView is only partially displayed on the screen. How do I get the CGRect that contains ONLY the visible portion of the UIView, AND, in the coordinates of the original view?
I've tried combinations of CGRectIntersect() for the UIView.frame rect and the [UIScreen mainscreen].bounds rect, like this:
CGRect rect = CGRectIntersection([UIScreen mainScreen].bounds,
view.frame);
I haven't been able to correctly resolve matching the coordinate systems.
You first need to translate the CGRect of your displaced view, to the coordinate system of the main screen.
You can try something like
if let mainVC = UIApplication.shared.keyWindow?.rootViewController {
let translatedRect = mainVC.view.convert(myTestView.frame, from: view)
let intersection = translatedRect.intersection(mainVC.view.frame)
}
This first finds the main rootViewController, and translates your view's frame to rootViewController's coordinate system, and then finds the intersection. This would work even if your displaced view is nested in multiple layers of views.
After some (hours of) experimentation I came up with this solution:
// return the part of the passed view that is visible
- (CGRect)getVisibleRect:(UIView *)view {
// get the root view controller (and it's view is vc.view)
UIViewController *vc = UIApplication.sharedApplication.keyWindow.rootViewController;
// get the view's frame in the root view's coordinate system
CGRect frame = [vc.view convertRect:view.frame fromView:view.superview];
// get the intersection of the root view bounds and the passed view frame
CGRect intersection = CGRectIntersection(vc.view.bounds, frame);
// adjust the intersection coordinates thru any nested views
UIView *loopView = view;
do {
intersection = [loopView convertRect:intersection fromView:loopView.superview];
loopView = loopView.superview;
} while (loopView != vc.view);
return intersection; // may be same as the original view frame
}
I first tried to convert the root view to the destination view's coordinates and then do the CGRectIntersect on the view frame, but it did not work. But I got it working the reverse way for UIViews with the root view as their superview. Then after some spelunking I figured out that I had to navigate thru the view hierarchy for subviews.
It works for UIViews where the superview is the root view and also for UIViews that are subviews of other views.
I tested it by drawing borders around these visible rects on the initial views, and it works perfectly.
BUT ... it does NOT work correctly if a UIView is scaled (!=1) AND a subview of another UIView other than the root view. The origin of the resultant visible rect is offset by a bit. I tried a few different ways to adjust the origin if the view is in a subview but I couldn't figure out a clean way to do it.
I've added this method to my utility UIView category, along with all the other "missing" UIView methods I've been developing or acquiring. (Erica Sadun's transform methods ...I'm not worthy...)
This does solve the problem I was working on though. So I will post another question regarding the scaling problem.
EDIT:
While working on the question and answer for the scaling issue, I came up with a better answer for this question too:
// return the part of the passed view that is visible
- (CGRect)getVisibleRect:(UIView *)view {
// get the root view controller (and it's view is vc.view)
UIViewController *vc = UIApplication.sharedApplication.keyWindow.rootViewController;
// get the view's frame in the root view's coordinate system
CGRect rootRect = [vc.view convertRect:view.frame fromView:view.superview];
// get the intersection of the root view bounds and the passed view frame
CGRect rootVisible = CGRectIntersection(vc.view.bounds, rootRect);
// convert the rect back to the initial view's coordinate system
CGRect visible = [view convertRect:rootVisible fromView:vc.view];
return visible; // may be same as the original view frame
}

Add custom UIView programmatically created inside XIB UIView

I've created a UIView programmatically that embeds several UIControls (UIButtons, UISwitchs and UILabels mainly). I set in the -(id)initWithFrame: of this class the background color.
I have created a message to add the UIControls in the view in order to put inside of my custom view. It's something like that:
-(void) CreateGuiObjects
{
UIView *container = self;
//create uiswitch
mOnOffSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(10, 20, 0, 0)];
mOnOffSwitch.translatesAutoresizingMaskIntoConstraints = NO; //used for constraint testing
//add parent view (self)
[container addSubview: mOnOffSwitch];
/*
other stuff like above
*/
}
then in my view controller there is an event handler for an external button; the action is to add the above custom view in an empty UIView created with Storyboard Interface Builder in Xcode.
the code is like the following:
-(void)CreateButton
{
MyCustomView *view = [MyCustomView alloc] initWithFrame:CGRectMake(20,20,300,200)];
[self.view addSubview: view];
//now call my create method
[view CreateGuiObjects];
}
now, the problem is that it draws the controls, but it seems to position them in a different place...i set the frame origin for the container view in (20,20) and then put the switch in (10,20) where this point is relative to the custom view origin. Instead of that position the view seem to be far away from that position and the second problem is that the background color (gray) set in the initWithFrame is totally ignored.
If i remove every call to addSubview inside the CreateGuiObjects, it draws the empty view with the correct background color and in the correct position.
Edit:
if remove `mOnOffSwitch.translatesAutoresizingMaskIntoConstraints = NO; it works fine...but if i put again it doesn't work. Need to understand deeply the meaning of this property.
Could someone can help me? i think it is a silly question but i'm new to iOS development :(
thanks in advice
Method translatesAutoresizingMaskIntoConstraints = YES means that the UIView is using Auto Layout.
The fundamental building block in Auto Layout is the constraint.
Constraints express rules for the layout of elements in your
interface; for example, you can create a constraint that specifies an
element’s width, or its horizontal distance from another element. You
add and remove constraints, or change the properties of constraints,
to affect the layout of your interface.
When calculating the runtime positions of elements in a user
interface, the Auto Layout system considers all constraints at the
same time, and sets positions in such a way that best satisfies all of
the constraints.
read more about Auto Layout Concepts
If you don't know how to use Auto Layout I would recommend you to turn it off.

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.

Storyboard white space at top in all model controllers

Am getting a white space at top in all containers. if I do a model transition that white space is clearly visible on top of all navigation controllers.
that space is around 20px.
How to remove this white space..??
Any suggestion ???
You didn't really give us enough details in your question, but the most likely cause for your problem is that you are instantiating the subviews programmatically for your viewcontrollers using the viewcontroller's view frame rather than the bounds.
If in your viewDidLoad method you do something like:
- (void)viewDidLoad
{
[super viewDidLoad];
MyView* myView = [[MyView alloc] initWithFrame:self.view.frame];
[self.view addSubview:myView];
}
then your view will be initialized with an offset of 20px, because your self.view.frame's origin is (0, 20), in order to not overlap with the navigation bar. If you then set your subview's frame with that same origin, it means that the view's origin will be 20 px further translated down (it's not really pixels but points, as the value stays the same whether it's a retina display or not, but whatever).
So, if you want to add a subview that is the size of the view controller's view that is presenting it, then you should always initialize it with:
MyView* myView = [[MyView alloc] initWithFrame:self.view.bounds];
I hope this helps, you didn't give us much to work with in your question, in the future try to give us (many many) more details about what it is that you do, both in the code and in the storyboard.

Getting subViews' frames in Storyboard

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.

Resources