Example
I have a UIView which simply draws a custom spinner using CoreGraphics. It is updating itself using CADisplayLink so it can draw each frame.
Another UIView subclass draws a progress-bar also using CoreGraphics. It is also updating itself using CADisplayLink.
The list could potentially go on. And as you can see codewise it is obviously easiest that each component has its own CADisplayLink.
Is it better or worse to have many CADisplayLinks? (A workaround to having multiple CADisplayLink instances would be to have one instance which sends to many delegates/blocks each callback)
Related
I would like to write a class that applies an effect to a UIView, similar to UIVisualEffectView. I would also like this view to update live -- if the "target" view redraws itself, my view should as well. Obviously I can just redraw on every frame using a CADisplayLink or NSTimer, but I would prefer not to waste the user's battery unnecessarily.
I see that CALayer/UIView objects have a needsDisplay boolean object, but this seems to be something that the client sets, rather than the system exposing it, so it doesn't seem to be any help.
Is it possible to do this?
Both have most of the same attributes, both support different kind of animations, both represent different data.
What are the differences between a UIView and a CALayer?
On iOS, every UIView is backed by a Core Animation CALayer, so you are dealing with CALayers when using a UIView, even though you may not realize it. Unlike NSViews on the Mac, which evolved before Core Animation existed, UIViews are intended to be lightweight wrappers around these CALayers.
As I describe in the similar question "When to use CALayer on the Mac/iPhone?", working directly with CALayers doesn't give you significant performance advantages over UIViews. One of the reasons you might want to build a user interface element with CALayers instead of UIViews is that it can be very easily ported to the Mac. UIViews are very different from NSViews, but CALayers are almost identical on the two platforms. This is why the Core Plot framework lays out its graphs using CALayers instead of other UI elements.
One thing UIViews provide over CALayers is built-in support for user interaction. They handle hit-testing on touches and other related actions that you would need to build yourself if managing a hierarchy of CALayers. It's not that hard to implement this yourself, but it is extra code you'd need to write when building a CALayer-only interface.
You will often need to access the underlying layers for a UIView when performing more complex animations than the base UIView class allows. UIView's animation capabilities have grown as the iOS SDK has matured, but there are still a few things that are best done by interacting with the underlying CALayer.
From the Ray Wenderlich blog (Tutorial)
CALayers are simply classes representing a rectangle on the screen
with visual content. “But wait a darn minute,” you may say, “that’s
what UIViews are for!” That’s true, but there’s a trick to that:
every UIView contains a root layer that it draws to!
Simply speaking,UIView inherit from UIResponder, handle events from users, contains CALayer, which inherit from NSObject, mainly focus on rendering, animation etc.
UIView is a container for CALayers. Using UIKit.
CALayer where we draw the contents. Using CoreGraphics
If you work with custom control like features it would be great to go ahead with single view containing more layers for accurate native rendering. Since CALayers are weightless than UIView.
To create common skeleton for Mac and iOS, follow the design for your app using CALayers. Since it is available in both platform.
UIView having feature like touch events achieved using delegates -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event, tochesStart like events and other UIKit features.
To work with CALayers use Core Graphics knowledge.For any simple view rendering UIView is enough.
UIView: Views have more complex hierarchy layouts. They can receive user interactions like taps, pinches, cliks and more. Working with UIViews happens on the main thread, it means it is using CPU power.
CALayer: Layers on other hand have simpler hierarchy. That means they are faster to resolve and quicker to draw on the screen. There is no responder chain overhead unlike with views. Layers are drawn directly on the GPU. It happens on a separate thread without burdening the CPU.
For more details: https://medium.com/#fassko/uiview-vs-calayer-b55d932ff1f5
The big difference is UIView is designed for CocoaTouch on mobile device. It adds some event handler which CALayer did not provide.
If I create a subclassed UIView and want to use a bunch of custom made content (animations via CABasicAnimation using CAShapeLayers, custom made drawings using CGContextRef in drawRect) when is the right time to create, add and animate sublayers?
I know that I should perform custom drawings in the drawRect method (and since there is the only place when I can actually get UIGraphicsGetCurrentContext() that kinda narrows my choice down). Now I have been creating sublayers, animations and all that other non-drawing related stuff in the drawRect as well. But I'm not sure that drawRect is the best place to do those kind of activities.
I am familiar with layoutSubviews method and some other UIView methods but haven't actually implemented any of them except drawRect.
So if I repeat myself one more time - the question goes: Where to add sublayers, where to animate them and are there any tricks or catches I should be aware of?
You can create and add the layers at init, if they are permanent. If they are dynamic, layoutSubviews is better. This obviously means you need to setNeedsLayout whenever a new layer/item is required. You can mix and match as much as you want between -init and -layoutSubviews, but if I had to pick, I'd say lean toward using layoutSubviews. Don't use drawRect.
You can set properties (strokeWidth, lineColor, path, etc) to a CAShapeLayer at either the time of creation or during normal execution. This includes setting a path to CAShapeLayer. Again, don't set properties in drawRect.
If you want to do custom drawing on the layer you can subclass a layer and use drawRect on that layer. This can be good if the CALAyers need to be reused and extended. You can also supply a CALayerDelegate, as long as its not the UIView. See these questions: Using CALayer Delegate AND
iOS: Using UIView's 'drawRect:' vs. its layer's delagate 'drawLayer:inContext:'
Animation is easy, and it follows the same principles as creating and setting properties. Make sure you understand when automatic animations will be invoked and how to disable them: Disabling implicit animations in -[CALayer setNeedsDisplayInRect:]
Again don't try to animate from drawRect: How to make my UIBezierPath animated with CAShapeLayer?
Drawing is expensive. Animation is cheap. Using drawRect too much will cause a big performance hit to your applications. Use drawRect/setNeedsDisplay sparingly, for instance on a state change (selected/unselected). Whenever possible modify views with animations or top level properties, and don't redraw the view. Making drawRect do anything OTHER than draw could result in you calling setNeedsDisplay unnecessarily. An easy upfront optimization is to call drawRect as little as possible, or not at all.
When creating an iOS app, I'm confused as to when exactly I need to call setNeedsDisplay? I know that it has something to do with updating/redrawing the UI; however, do I need to call this every time I change any of my views?
For example, do I need to call it:
After programatically changing the text in a text field
When changing the background of a view?
When I make changes in viewDidLoad?
How about in viewDidAppear?
Could someone give me some general guidelines regarding when to use this method?
You should only be calling setNeedsDisplay if you override drawRect in a subclass of UIView which is basically a custom view drawing something on the screen, like lines, images, or shapes like a rectangle.
So you should call setNeedsDisplay when you make changes to few variables on which this drawing depends and for view to represent that change , you need to call this method which internally will give a call to drawRect and redraw the components.
When you add an imageView or a UIButton as a subview or make changes to any subview, you need not call this method.
Example:
You have a view that shows a moving circle, either you touch and move it, or may be timer based animation.
Now for this, you will need a custom view that draws a circle at given center and with given radius.
These are kept as instance variables which are modified to move the circle by changing its center or make it bigger by increasing radius of it.
Now in this case either you will modify these variables(centre or radius) in a loop and timer Or may be by your fingers in touchesEnded and touchesMoved methods.
To reflect the change in this property you need to redraw this view for which you will call setNeedsDisplay.
You only really need to call -setNeedsDisplay on UIView subclasses that draw their contents with -drawRect:.
For labels and other standard controls, changing the text will automatically cause the label to redraw so you don't need to do this yourself.
setNeedsDisplay: should be called when you want to refresh your view explicitly. It just sets an internal flag, and the iOS UI system will call drawRect: at an appropriate time later.
It sounds like it should be always called when you updating any property which may change the presentation. But it's not. Almost all the standard UI controls already handled that. I believe whenever you modify the properties of standard UI components (views), setNeedsDisplay: would be triggered internally, and the affected region will be redrawn. (In all the situations you listed)
However, if you create your own view, implement its own drawRect:, and want to update that when something has been changed, you must call setNeedsDisplay: explicitly.
I think #Amogh Talpallikar make it clear. And I just wanna discuss one thing more.
In the fact that, you should avoid override drawRectunless you really need it because it can cause bad performance. You can refer this https://yalantis.com/blog/mastering-uikit-performance/
If you only wanna change frame, position of buttons, labels, ... you can call setNeedLayout or layoutIfNeeded
You will call setNeedDisplay when you are changing the property on which your view custom drawing depends. It will explicitly call drawRect: method forcefully.
Both have most of the same attributes, both support different kind of animations, both represent different data.
What are the differences between a UIView and a CALayer?
On iOS, every UIView is backed by a Core Animation CALayer, so you are dealing with CALayers when using a UIView, even though you may not realize it. Unlike NSViews on the Mac, which evolved before Core Animation existed, UIViews are intended to be lightweight wrappers around these CALayers.
As I describe in the similar question "When to use CALayer on the Mac/iPhone?", working directly with CALayers doesn't give you significant performance advantages over UIViews. One of the reasons you might want to build a user interface element with CALayers instead of UIViews is that it can be very easily ported to the Mac. UIViews are very different from NSViews, but CALayers are almost identical on the two platforms. This is why the Core Plot framework lays out its graphs using CALayers instead of other UI elements.
One thing UIViews provide over CALayers is built-in support for user interaction. They handle hit-testing on touches and other related actions that you would need to build yourself if managing a hierarchy of CALayers. It's not that hard to implement this yourself, but it is extra code you'd need to write when building a CALayer-only interface.
You will often need to access the underlying layers for a UIView when performing more complex animations than the base UIView class allows. UIView's animation capabilities have grown as the iOS SDK has matured, but there are still a few things that are best done by interacting with the underlying CALayer.
From the Ray Wenderlich blog (Tutorial)
CALayers are simply classes representing a rectangle on the screen
with visual content. “But wait a darn minute,” you may say, “that’s
what UIViews are for!” That’s true, but there’s a trick to that:
every UIView contains a root layer that it draws to!
Simply speaking,UIView inherit from UIResponder, handle events from users, contains CALayer, which inherit from NSObject, mainly focus on rendering, animation etc.
UIView is a container for CALayers. Using UIKit.
CALayer where we draw the contents. Using CoreGraphics
If you work with custom control like features it would be great to go ahead with single view containing more layers for accurate native rendering. Since CALayers are weightless than UIView.
To create common skeleton for Mac and iOS, follow the design for your app using CALayers. Since it is available in both platform.
UIView having feature like touch events achieved using delegates -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event, tochesStart like events and other UIKit features.
To work with CALayers use Core Graphics knowledge.For any simple view rendering UIView is enough.
UIView: Views have more complex hierarchy layouts. They can receive user interactions like taps, pinches, cliks and more. Working with UIViews happens on the main thread, it means it is using CPU power.
CALayer: Layers on other hand have simpler hierarchy. That means they are faster to resolve and quicker to draw on the screen. There is no responder chain overhead unlike with views. Layers are drawn directly on the GPU. It happens on a separate thread without burdening the CPU.
For more details: https://medium.com/#fassko/uiview-vs-calayer-b55d932ff1f5
The big difference is UIView is designed for CocoaTouch on mobile device. It adds some event handler which CALayer did not provide.