CALayer sublayer not graceful during device rotation - ios

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.

Related

How to fil rectangular ares in the screen with good performance in ios

I'm making simple number coloring game (like sandbox, pixel art, unicorn, etc) and came across a problem. I tried various methods of filling rects in the screen.
At first i created a backgroundView(a simple UIView) which had 2500 subviews(also UIViews), each one had size = (CGSize){50,50}. Added a tap gesture recognizer, detected which view should be filled, and simply changed the background color of that view. But when i placed the backgroundView in the scrollView, the scroll and zooming were awful.
Tried same thing but this time each rectangle was a CALayer. So a backgroundView, which had 2500 sublayers(each was a CALayer with 50,50 size) - zoom and scroll was also awful.
Tired to use custom drawing with overriding drawRect method in UIView subclass or drawInContext in CALayer subclass(i was filling rects with CGContext), this time problem was also caused by the zooming and scrolling. Because i needed to update every rect when user zooms, it was triggering a lot of drawRect calls, and performance was also bad.
Any thoughts how can i fill rects in the screen ?
Explanation
Yup! Adding 2,500 tiny UIViews to a scroll view can destroy performance. I wouldn't use that approach, but if you do, at least make sure that subview_N.opaque = YES; on all subviews to disable compositing.
The CALayer approach that you described is basically the same as the UIView approach because views are backed by CoreAnimation layers (see -[UIView layer]).
The best options for drawing thousands of rectangles to the screen on iOS without decimating frame rate, is to use one of the following approaches:
Draw directly using CoreGraphics
Draw directly using OpenGL (extremely involved)
Use a layout engine such as UICollectionViewLayout
Now, you said you had tried overriding -[UIView drawRect:], but you didn't provide any example code for us to checkout. Please update your question with actual code for method #3 if you want more specific feedback. It's very likely that something is wrong with the drawRect code you created. CoreGraphics can definitely draw thousands of squares on screen without dragging frame rate down that badly.
One Solution
I recently released a project, YMTreeMap, that draws thousands of rectangles into a UIView to create financial TreeMaps. It's in Swift, not Objective-C, but the underlying concepts are the same. For this, I created a custom UICollectionViewLayout that lets Apple's well tested UICollectionView class handle the nitty gritty of selective drawing, zooming and animation.
The example UIViewController code in the YMTreeMap project shows how to draw thousands of colored rectangles to the screen if all you know is their location and size. This sounds like what you are doing. Since you're also scrolling and zooming, this solution might be perfect for you because UICollectionView has native support for both of those.

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.

Draw rounded frame in UIView:drawRect, gets skewed after rotate/resize

Okay... so I am getting the hang of iOS, have created a UIView with a combination of widgets (buttons, UITableViews) content and some rendering in drawRect.
The rendering is actually to draw a rounded rectangle within the UIView frame, with circular corners. Then I add this view to my main view.
In the rotation animation, I change the position and aspect ratio of my UIView and called a layout method, and was pleased that it would smoothly change its shape and reposition its contents.... EXCEPT: something odd happens to my rendering... they sort of get squished, and my rounded corners are now elliptical/squished (even though the rendering code always makes circles) like the UIView applies a transform after the drawRect, but does;t re-render.
This is a very surprising effect, can someone give me a hint to what may be going on? I want the rendering to be consistent and sensitive to the current rectangle.
EDIT: Added pictures. The UIView onDraw renders framing and headers, and there are two UITableViews as children. It starts up in portrait mode (figure 1) and looks fine. When the parent view rotates I initiate an animation that changes the sizing of the subview to be suitable to landscape. The sub UITableViews resize fine, but the rendering is now squished (Figure 2).
You need to set the contentMode of your UIView to Redraw

Best way to draw line between fix CGPoint and moving UIView object's center point

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.

Notification when visible area of CALayer changes?

I have a CALayer for which I provide content for only the visible area (somewhat similar to CATiledLayer). The problem is there does not seem to be a way to receive notification when the visible area of the CALayer changes so that displayLayer is called. I currently subclass and hook setPosition, setBounds, and setTransform, but this doesn't catch the cases where a superview/layer changes (for example, UIScrollView scrolls by changing the scroll views origin ). I'm left hooking parent views and sprinkling setNeedsDisplay all over the code.
Is there a better way?
The currently visible rect is [CALayer visibleRect]. This is set by the scroll view (layer) and is what you're expected to base drawing on in scroll views.
You probably want to override -needsDisplayOnBoundsChange to return YES. That's typically how you handle most of what you're describing.
If you want things like position to force a redraw (that's unusual, but possible), then you can override +needsDisplayForKey: to return YES for any key changes that you want to force a redraw.
If you want to make sure you're only drawing what you need to draw, then you should be checking your clipping box using CGContextGetClipBoundingBox() during your drawing code.

Resources