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.
Related
I drew a circle directly on a view's layer, a device rotation looks perfect.
The drawing occurs in the func draw(_ rect: CGRect) method.
I then drew the circle on a sublayer and added it to the view in the init method.
I update the sub layer's frame in the func draw(_ rect: CGRect) method.
It seems to me like there are times that the sublayer is being updated before the device rotation is complete.
Hence the first image below.
A quick fix to make this look a bit better is to clip the subview layers.
This way the circle won't bleed to the main view (orange view).
Although this isn't fixing the main issue.
I don't know why adding a sublayer is causing this issue.
I'm also drawing the content of the sublayer in the draw method of it's super view. The orange view is not the super view, the super view is the rectangle in the middle
During Rotation
Note: I did not add example code because all examples that I have seen have the same issue.
Others fix this by hardcoding the size of a view, hence the rotation will not resize the view and the rotation will not cause this.
They also, tend to lock the view in portrait mode.
Although, if the example code is needed I can create a simple example.
I found a solution to the issue.
It seems to me like everyone else has the same issues.
This is because layers don't have constraints.
We don't see these issues on production apps because apps are usually locked in portrait mode.
It also seems like apple tends to use images for their controls and avoids drawing.
This is according to a Ray Wenderlich article that I read.
The solution is basically to use subviews (instead of sublayers) and use the layer built into the sub view to draw.
This article explains the process: https://marcosantadev.com/calayer-auto-layout-swift/
Here is an image showing the rotation with a subview and sublayers.
The subview gracefully rotates because it has constraints.
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.
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.
I have a UIView subclass object that animates and therefore changes its position over time as a subview in my UIViewController's view. Actually my moving UIView subclass is just an image of a ball and it's moving as if it was hanging down from my devices screens top border. But to be a real pendulum I'd like to also draw a line between my ball and the CGPoint it hangs down from on top of my screen.
My idea was to just draw a line every time the UIView changes its position. But as the moving is done within an iOS API (I'm just calling something like [myBallView swing]) I can't do this at the same place the movement is happening. I'm actually not animating the view myself.
The only solution I have in my mind to solve my issue is pretty bad: subclassing UIView, adding it as a superview to my moving UIView and adding a line every time drawRect is called. But I'm not even sure drawRect is going to be called there. Either way, there must be a better solution for this.
Does anyone know a good solution to my problem?
Making a custom subclass of UIView as the superview is reasonable.
However, rather than drawing the line yourself, I would suggest implementing +layerClass in your custom view, and making the backing layer a CAShapeLayer.
Then you can create a CGPath in the view's shape layer that is a line, and manipulate the start and end points of the line when your other view's center moves. Shape layers are designed to draw as part of the screen update process, and you could even make the change animate by changing the endpoints of the path with a CABasicAnimation.
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.