Question about bounds/frame of custom class derived from UIView - ios

I have a class derived from UIView, it represents a square area(280x280) over which objects will be drawn, but somehow only the upper portion(280x187) was drawn.
I checked the bounds and frame within the initWithCoder and drawRect of the custom view class, the results are:
initWithCoder-->bounds:(280x280); frame:(280x280)
drawRect------->bounds:(280x187); frame:(280x187)
What could possible happen between initWithCoder and drawRect methods that changed both the bounds and frame?
Thanks for the information.

Inside InterfaceBuilder, I turned off the autoresize of view and took out the springs and struts, then the view displayed properly.

Related

When does a UIView know how big it is?

I have a subclass of UIView that draws a picture onto itself using CAShapeLayers. I want the drawing to adapt to whatever size the UIView is given by AutoLayout. I gave it a drawGraph() function that gets its current size from .bounds.size and then draws a picture to fit.
I started by calling my drawing function in the container UIViewController's viewDidLoad(), but at that point the layout hasn't happened yet so it has whatever bounds it had in the storyboard.
I tried calling it from the view controller's viewDidLayoutSubviews(), but that isn't totally satisfactory either. It gets called twice: first with the bounds from the storyboard, then again with the updated (correct) bounds. There's a pause in between, so the draw and redraw is visible to the user.
How can I tell if a UIView has had its layout constraints applied and is ready to give me accurate dimensions?
You should draw it in UIView's drawRect(rect: CGRect). Once view needs to draw on screen, this function will be called.
In my subclasses of UIView I usually do the code related to layout by overriding the layoutSubview() method. If I remember correctly it's more lightweight than drawRect(rect: CGRect).
But if you're really doing only drawing then you should probably do it in drawRect(rect: CGRect), as that's what Xcode suggests you when you create a new UIView subclass.

What method is called on UIView when it's resized by autolayout?

I have an image view that I'm implementing rounded corners on by overriding the following in my subclass:
-(void)setFrame:(CGRect)frame {
[super setFrame:frame];
[self.layout setCornerRadius:frame.size.width/10.0];
}
This works great for the initial display of the image, but if the size of the image changes due to device rotation or some other mechanism, this method does not get called to implement the resize.
I'm using autolayout, and I want to know what method of UIView (and thus UIImageView) is being called when my constraints resize the view so that I can recalculate my corner radius whenever this resize takes place. My (apparently false) assumption was that the autolayout system called setFrame: to move / resize views as needed.
From the docs on updateConstraints:
Custom views that set up constraints themselves should do so by overriding this method
....
Before layout is performed, your implementation of updateConstraints will be invoked, allowing you to verify that all necessary constraints for your content are in place at a time when your custom view’s properties are not changing.
Or from the docs on layoutSubviews:
You should override this method only if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want. You can use your implementation to set the frame rectangles of your subviews directly.
but if you need to do this on rotational changes, check out willTransitionToTraitCollection:withTransitionCoordinator:
Implementors of this method can use it to adapt the interface based on the values in the newCollection parameter. A common use of this method is to make changes to the high-level presentation style when the current size class changes.

Custom UIView drawRect is called even with UIViewContentModeScaleAspectFill

I'm having the following issue.
Suppose I have my own image view class (to make it easy I've trimmed all the code and just included only the code that is causing the issue). That class inherits from UIView and overrides drawRect. Here is how my drawRect looks (the code is in Xamarin, but it's easy to understand)
public override void Draw(RectangleF rect)
{
base.Draw(rect);
CGContext context = UIGraphics.GetCurrentContext();
if (Image != null)
{
var imageRect = getAspectFillRect(rect, Image.Size);
context.DrawImage(imageRect, Image.CGImage);
}
else
{
this.BackgroundColor.SetFill();
context.FillRect(this.Bounds);
}
}
This code simply draws the image set to Image property in a way which simulates UIImageView's aspect fill option by calculating the rect which will display the image in aspect fill mode using this line of code getAspectFillRect(rect, Image.Size);
I also have UICollectionView with custom layout and custom cells. Each UICollectionViewCell UI is defined in an xib file, there I have all the necessary constraints set and I there I also have my custom view for displaying images. For that view in interface builder I've set content mode to "Aspect Fill". The custom UICollectionViewLayout is made in a way that cell expands (you'll see the example shortly). So the image inside the cell should also scale, and as I've set "Aspect Fill" option, it shouldn't call drawRect of my custom view, rather it should just scale already drawn content. But that is not the case!
LOOK HERE to see the video which demonstrates what happens.
You may see that the image inside is not growing together with the cell. The problem is that before ever the cell expanding animation begins drawRect of my custom UIView is called with the rect which will eventually be established after the animation. So I get a jerky animation. In my custom layout code I just call LayoutIfNeeded after updating the constraints of the cell. I don't call setNeedsDisplay, so I don't get why my drawRect is called.
For experiment I replaced my custom image view with UIImageView, I set it's content mode to "Aspect Fill", and LOOK HERE what happened. YEAH! It worked. So I suppose the issue is not in the custom UICollectionView layout, rather in my custom image drawing class.
What I should take into account?? How I should handle this case? Any Ideas?
Thanks!
DrawRect will be called whenever the system "feels" it should call it. Regardless if you call SetNeedsDisplay or not.
Now, setting ContentMode to ScaleAspectFill does not mean that DrawRect will not be called. In fact, from Apple docs here:
Instead of redrawing the contents of the view every time, you can use
this property to specify that you want to scale the contents (either
with or without distortion) or pin them to a particular spot on the
view.
This means that, if you don't want to go through the hassle of drawing the contents yourself, just set the ContentMode property. In your example, you are using DrawRect to draw.
Now, the fact that DrawRect is called before the animation starts, but with the target value for Bounds (or Frame?) means that the target value of these properties is set before the change finishes. During an animation, these properties do not change. Note during. A Bounds' or Frame's value will not change through all the values of an ongoing transition. It only knows "start" and "end".
What would happen in your app if DrawRect was not called before the animation start, but was called after animation end? Well, the cell would resize along with its subview, but your image would remain small throughout the animation and snap to the large size after the animation finished. So it's one way or the other.
Solutions:
Use the UIImageView.
If you do need to use a custom view, use a UIImageView on that to display your image.
Draw your image on a custom CALayer and add that layer on your custom view's Layer.

Create a UIView Subclass which auto arranges its sub views according to its frame?

I have requirement like - I have to create a subclass of UIView (eg. MyView) in which it will have 4 sub views (UIImageVeiws, UILabels) arranged 2X2 in position.When I create object of my UIView subclass (MyView object) and gives a default frame those 4 sub views within the view should be arranged according to its super views frame which I am passing.
If I resize or reframe MyView object frame, the 4 subviews sholud get rearraged (size, spacing, origin) according the new frame which I have given to its super view.
I am facing problems to achieve this, can anyone help me out how to achieve this. My code should be generic with respect to my subclass. Even If i remove any of the 4 subview's the remaining 3 views should get arranged within its entire super view.
Thank you.
The method you need to implement is -(void)layoutSubviews.
This will be called automatically whenever the frame of your view changes, and at other appropriate times. Inside this method you can access the size of the parent view and layout the various subviews you have.
If you need to force a layout at a particular time, for example after changing certain properties on your view, you can call [view setNeedsLayout].

When is layoutSubviews called?

I have a custom view that's not getting layoutSubview messages during animation.
I have a view that fills the screen. It has a custom subview at the bottom of the screen that correctly resizes in Interface Builder if I change the height of the nav bar. layoutSubviews is called when the view is created, but never again. My subviews are correctly laid out. If I toggle the in-call status bar off, the subview's layoutSubviews is not called at all, even though the main view does animate its resize.
Under what circumstances is layoutSubviews actually called?
I have autoresizesSubviews set to NO for my custom view. And in Interface Builder I have the top and bottom struts and the vertical arrow set.
Another part of the puzzle is that the window must be made key:
[window makeKeyAndVisible];
of else the subviews are not automatically resized.
I had a similar question, but wasn't satisfied with the answer (or any I could find on the net), so I tried it in practice and here is what I got:
init does not cause layoutSubviews to
be called (duh)
addSubview: causes
layoutSubviews to be called on the
view being added, the view it’s being
added to (target view), and all the
subviews of the target
view setFrame
intelligently calls layoutSubviews on
the view having its frame set only
if the size parameter of the frame is
different
scrolling a UIScrollView
causes layoutSubviews to be called on
the scrollView, and its superview
rotating a device only calls
layoutSubview on the parent view (the
responding viewControllers primary
view)
Resizing a view will call layoutSubviews on its superview (Important: views with an intrinsic content size will re-size if the content that determines their size changes; for example, updating the text on a UILabel will cause the intrinsic content size to be updated and thus call layoutSubviews on its superview)
My results - http://blog.logichigh.com/2011/03/16/when-does-layoutsubviews-get-called/
Building on the previous answer by #BadPirate, I experimented a bit further and came up with some clarifications/corrections. I found that layoutSubviews: will be called on a view if and only if:
Its own bounds (not frame) changed.
The bounds of one of its direct subviews changed.
A subview is added to the view or removed from the view.
Some relevant details:
The bounds are considered changed only if the new value is different, including a different origin. Note specifically that is why layoutSubviews: is called whenever a UIScrollView scrolls, as it performs the scrolling by changing its bounds' origin.
Changing the frame will only change the bounds if the size has changed, as this is the only thing propagated to the bounds property.
A change in bounds of a view that is not yet in a view hierarchy will result in a call to layoutSubviews: when the view is eventually added to a view hierarchy.
And just for completeness: these triggers do not directly call layoutSubviews, but rather call setNeedsLayout, which sets/raises a flag. Each iteration of the run loop, for all views in the view hierarchy, this flag is checked. For each view where the flag is found raised, layoutSubviews: is called on it and the flag is reset. Views higher up the hierarchy will be checked/called first.
https://developer.apple.com/library/prerelease/tvos/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1
Layout changes can occur whenever any of the following events happens
in a view:
a. The size of a view’s bounds rectangle changes.
b. An interface orientation change occurs, which usually triggers a change in the root view’s bounds rectangle.
c. The set of Core Animation sublayers associated with the view’s layer changes and requires layout.
d. Your application forces layout to occur by calling the setNeedsLayout or layoutIfNeeded method of a view.
e. Your application forces layout by calling the setNeedsLayout method of the view’s underlying layer object.
Some of the points in BadPirate's answer are only partially true:
For addSubView point
addSubview causes layoutSubviews to be called on the view being added, the view it’s being added to (target view), and all the subviews of the target.
It depends on the view's (target view) autoresize mask. If it has autoresize mask ON, layoutSubview will be called on each addSubview. If it has no autoresize mask then layoutSubview will be called only when the view's (target View) frame size changes.
Example: if you created UIView programmatically (it has no autoresize mask by default), LayoutSubview will be called only when UIView frame changes not on every addSubview.
It is through this technique that the performance of the application also increases.
For the device rotation point
Rotating a device only calls layoutSubview on the parent view (the responding viewController's primary view)
This can be true only when your VC is in the VC hierarchy (root at window.rootViewController), well this is most common case. In iOS 5, if you create a VC, but it is not added into any another VC, then this VC would not get any noticed when device rotate. Therefore its view would not get noticed by calling layoutSubviews.
I tracked the solution down to Interface Builder's insistence that springs cannot be changed on a view that has the simulated screen elements turned on (status bar, etc.). Since the springs were off for the main view, that view could not change size and hence was scrolled down in its entirety when the in-call bar appeared.
Turning the simulated features off, then resizing the view and setting the springs correctly caused the animation to occur and my method to be called.
An extra problem in debugging this is that the simulator quits the app when the in-call status is toggled via the menu. Quit app = no debugger.
calling
[self.view setNeedsLayout];
in viewController makes it to call viewDidLayoutSubviews
have you looked at layoutIfNeeded?
The documentation snippet is below. Does the animation work if you call this method explicitly during the animation?
layoutIfNeeded
Lays out the subviews if needed.
- (void)layoutIfNeeded
Discussion
Use this method to force the layout of subviews before drawing.
Availability
Available in iPhone OS 2.0 and later.
When migrating an OpenGL app from SDK 3 to 4, layoutSubviews was not called anymore. After a lot of trial and error I finally opened MainWindow.xib, selected the Window object, in the inspector chose Window Attributes tab (leftmost) and checked "Visible at launch". It seems that in SDK 3 it still used to cause a layoutSubViews call, but not in 4.
6 hours of frustration put to an end.

Resources