Override UIView's frame - iphone

I have subclassed UIView, but I need to prevent the frame from changing, so I tried overriding the setFrame: method and just ignoring the value passed, and creating my own CGRect base on self.superview and passing it to [super setFrame:
How can I make the UIView's frame unchangeable? (from within the subclass)

Step1: Override setFrame to do nothing.
- (void)setFrame:(CGRect)newRect
{
}
Step 2: Move your semi-static frame setting into your overridden version of willMoveToSuperview: to get a valid superview reference.
- (void)willMoveToSuperview:(UIView *)newSuperview
{
CGRect newFrame = newSuperview.frame;
//manipulate the frame here
[super setFrame:newFrame];
}

Wait, can't you just set the autoresizingMask to 0? I though that would prevent the view from getting resized when parent view is resized (tried it as well, and seemed to work for me).

Related

Custom UIView layoutSubviews with orientation and animations?

I'm in a dilemma which method to use for setting frames of custom UIViews with many subviews in it and still have animations and automatically adjust to rotations. What I usually do when I create a new viewcontroller is alloc my custom view in loadView or viewDidLoad, e.g:
-(void)viewDidLoad
{
[super viewDidLoad];
detailView = [[DetailView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
detailView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
self.view = detailView;
}
Normally this width & height is not correct for an iPhone5-screen (the actual view-frame is not set until viewWillAppear) but because of the autoresizingmask it all works out.
Then in the initWithFrame of the custom UIView DetailView, I alloc all subviews with CGRectZero, e.g:
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
label = [[UILabel alloc] initWithFrame:CGRectZero];
[self addSubview:label];
}
}
Then I override layoutsubviews to set all frames of all subviews. This works perfectly for any screen size and any orientation, e.g:
-(void)layoutSubviews
{
[super layoutSubviews];
label.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
}
However, I just found out that layoutSubviews is not so great when you use animations, because when you use animations in an animationblock, layoutsubviews is called in the middle of the animation and it completely breaks the animation, e.g:
-(void)animateLabel
{
[UIView animateWithDuration:0.4f animations:^
{
label.transform = CGAffineTransformMakeTranslation(100, 100);
}];
}
I believe there are ugly workarounds this by using flags for each animation and in layoutsubviews use those flags to set the correct start or endframe of the animated block but I don't think I should have to create a flag for each animation I want to do.
So my problem is now: how am I supposed to have a custom UIView WITH animations that also automatically adjusts itself to rotations?
The only solution I can come up with right now (that I don't like):
Don't use layoutSubviews but use the setFrame/setBounds method of the custom UIView to set the frames of all subviews. Then check in the viewController every time a rotation occurs and then use the setFrame/setBounds method of the custom UIView to change all frames of all subviews. I don't like this solution because the rotation methods are different in iOS5 and iOS6 and I don't want to have to do this in every UIViewController with it's own custom UIView.
Any suggestions?
I have recently started overriding viewDidLayoutSubviews (many times instead of viewWillAppear) in my UIViewControllers.
Yes viewDidLayoutSubviews is called on rotations. (from comment)
The method fires after all the internal layouts have already been completed so all finalized frames should be setup, but still give you the time you need to make adjustments before the the view is visible and shouldn't have any issues with animations because you are not already inside an animation block.
viewcontroller.m
- (void)viewDidLayoutSubViews {
// At this point I know if an animation is appropriate or not.
if (self.shouldRunAnimation)
[self.fuView runPrettyAnimations];
}
fuView.m
- (void)runPrettyAnimations {
// My animation blocks for whatever layout I'd like.
}
- (void)layoutSubviews {
// My animations are not here, but non animated layout changes are.
// - Here we have no idea if our view is visible to the user or may appear/disappear
// partway through an animation.
// - This also might get called far more than we intend since it gets called on
// any frame updates.
}

How to find out the exact frame of a UIViewController's view?

When is it possible to know the exact size of a UIViewController's view?
The Problem
I have a multi-line UILabel whose frame depends of its text and the width of the parent view. Given that I need to position other views below the UILabel, it's important to make its frame cover exactly the space of the text.
I currently calculate the size like this on viewDidLoad:
labelSize = [text sizeWithFont:font constrainedToSize:CGSizeMake(self.view.frame.size.width, MAX_HEIGHT)];
The problem is that the width of the parent view changes when the UIViewController is used as a modal form sheet or a popover. If I use autoresizingMask the frame of the UILabel is adjusted accordingly, but it no longer is an exact fit for the text.
Where can I calculate the frame of this UILabel knowing the exact size of the UIViewController's view?
Debugging Efforts
This is the result of printing self.view.frame when showing the UIViewController as a modal form sheet (UIModalPresentationFormSheet).
viewDidLoad: (0.000000;0.000000;320.000000;480.000000)
viewWillAppear: (0.000000;0.000000;768.000000;960.000000)
afterDelay: (0.000000;0.000000;540.000000;576.000000)
The code that produces the above output:
- (void)viewDidLoad {
[super viewDidLoad];
[Utils logFrame:self.view.frame tag:#"viewDidLoad"];
}
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[Utils logFrame:self.view.frame tag:#"viewWillAppear"];
[self performSelector:#selector(afterDelay) withObject:nil afterDelay:0.1];
}
- (void) afterDelay {
[Utils logFrame:self.view.frame tag:#"afterDelay"];
}
That's because your UIViewController's root view is using autoresizingMask. I don't think there is a "perfect timing" to detect the size of your UIViewController's root view unless you override your root view's 'layoutSubviews' method.
If you don't want your view to be automatically resized, just don't use autoresizing and set the size by yourself, it will always be the same size you expected.
If you are not sure what kind of autoresizingMasks your root view is using, NSLog is your friend.
This looks like a bug, the value should be known in viewWillAppear.
The alternative is to subclass your UIView and override layoutSubviews there to arrange the subviews based on content. You'll then have to call [self.view setNeedsLayout] from your view controller every time you update the contents of the label. The system will call it if the view resizes, so that should have you covered.
Try this for finding the label size,
labelSize = [text sizeWithFont:font constrainedToSize:CGSizeMake(self.label.frame.size.width, MAX_HEIGHT)];
Instead of using the view's width, use the label's width in the constrain size. This should be available in the viewDidLoad, viewDidApper methods of the view controller.
try viewWillAppear. There is no reason for not knowing the size before the the view is about to shown

Need UIView to autoresize

I have made a custom UIView which is shown when the user hits a button in the navigationbar. I make my view's in code. In my loadview I set the autoresizing masks and the view loads correct on screen. However the UIView which is shown when the user taps the button does not resize even when I have set the autoresizing masks.
UIView *blackView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 416.0)];
blackView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
Do I need to use self.view.frame.size.width and self.view.frame.size.height instead? And if I do why? Does not resizing masks work outside of loadView?
Thank you for your time:)
the autoresizingMask affects how a view will behave when its superviews frame changes. if all you are doing is showing theblackViewwhen you tap a button, thenblackView` will have whatever frame you initially set for it.
If this isn't enough info, please post some more code around how you are configuring and displaying blackView and it's superview and explain more about what situations you are expecting blackView to resize in. Rotation is one of them, if that's what you're concerned with.
First things first, I hope you've done this:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
Let's say the view that needs resizing is: view2
The view that has view2 as a subview is: view1
While creating view1 you would declare it as:
view1 = [[UIView alloc] init];
[view1 setNeedsLayout];
Now in view1's .m file you need to overload the layoutSubviews method as shown:
- (void)layoutSubviews
{
CGRect frame = view2.frame;
// apply changes to frame
view2.frame = frame;
}
In case view1 is a view controller's view, you need to do that same thing as above in the willRotate method as shown
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
CGRect frame = view2.frame;
// apply changes to frame
view2.frame = frame;
}
This is a tried and tested method that I use to handle orientation changes.

Making view resize to its parent when added with addSubview

I'm having a problem when using addSubview.
Example code:
ParentView *myParentView = [[ParentView alloc] initWithNibName:#"ParentView " bundle:nil];
ChildeView *myChildeView = [[ChildeView alloc] initWithNibName:#"ChildeView" bundle:nil];
//... parent frame resized with setFrame lets say to x:0, y:0, W:320, H:411
[[myParentView view] addSubview: [myChildeView view]];
My child when added is bigger then the parent, and does not resize its frame to parent bounds. I can't use "clip subviews" on the parent, and "Autoresize Subviews" seems not to work if the parent frame is not resized again. Is there a property that makes a subview resize automatically to its parent's bounds without using setFrame on every child?
If you aren’t using Auto Layout, have you tried setting the child view’s autoresize mask? Try this:
myChildeView.autoresizingMask = (UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight);
Also, you may need to call
myParentView.autoresizesSubviews = YES;
to get the parent view to resize its subviews automatically when its frame changes.
If you’re still seeing the child view drawing outside of the parent view’s frame, there’s a good chance that the parent view is not clipping its contents. To fix that, call
myParentView.clipsToBounds = YES;
Just copy the parent view's frame to the child-view then add it. After that autoresizing will work. Actually you should only copy the size CGRectMake(0, 0, parentView.frame.size.width, parentView.frame.size.height)
childView.frame = CGRectMake(0, 0, parentView.frame.size.width, parentView.frame.size.height);
[parentView addSubview:childView];
that's all you need
childView.frame = parentView.bounds
Tested in Xcode 9.4, Swift 4
Another way to solve this issue is , You can add
override func layoutSubviews() {
self.frame = (self.superview?.bounds)!
}
in subview class.
Swift 4 extension using explicit constraints:
import UIKit.UIView
extension UIView {
public func addSubview(_ subview: UIView, stretchToFit: Bool = false) {
addSubview(subview)
if stretchToFit {
subview.translatesAutoresizingMaskIntoConstraints = false
leftAnchor.constraint(equalTo: subview.leftAnchor).isActive = true
rightAnchor.constraint(equalTo: subview.rightAnchor).isActive = true
topAnchor.constraint(equalTo: subview.topAnchor).isActive = true
bottomAnchor.constraint(equalTo: subview.bottomAnchor).isActive = true
}
}
}
Usage:
parentView.addSubview(childView) // won't resize (default behavior unchanged)
parentView.addSubview(childView, stretchToFit: false) // won't resize
parentView.addSubview(childView, stretchToFit: true) // will resize
You can always do it in your UIViews - (void)didMoveToSuperview method. It will get called when added or removed from your parent (nil when removed). At that point in time just set your size to that of your parent. From that point on the autoresize mask should work properly.

How do I tell if a UIView is visible and on screen?

If I have a UIView (or UIView subclass) that is visible, how can I tell if it's currently being shown on the screen (as opposed to, for example, being in a section of a scroll view that is currently off-screen)?
To maybe give you a better idea of what I mean, UITableView has a couple of methods for determining the set of currently visible cells. I'm looking for some code that can make a similar determination for any given UIView.
Not tried any of this yet. But CGRectIntersectsRect(), -[UIView convertRect:to(from)View] and -[UIScrollView contentOffset] seem to be your basic building blocks here.
Here's what I used to check which UIViews were visible in a UIScrollView:
for(UIView* view in scrollView.subviews) {
if([view isKindOfClass:[SomeView class]]) {
// the parent of view of scrollView (which basically matches the application frame)
CGRect f = self.view.frame;
// adjust our frame to match the scroll view's content offset
f.origin.y = _scrollView.contentOffset.y;
CGRect r = [self.view convertRect:view.frame toView:self.view];
if(CGRectIntersectsRect(f, r)) {
// view is visible
}
}
}
if you are primarily worried about releasing an object that is not in the view hierarchy, you could test to see if it has a superview, as in:
if (myView.superview){
//do something with myView because you can assume it is on the screen
}
else {
//myView is not in the view hierarchy
}
I recently had to check whether my view was onscreen. This worked for me:
CGRect viewFrame = self.view.frame;
CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
// We may have received messages while this tableview is offscreen
if (CGRectIntersectsRect(viewFrame, appFrame)) {
// Do work here
}