Animating (sliding) a view with content created in drawRect: - ios

I have some views that have content, textures and shadows mostly, that need to be moved by sliding them to another part of the screen.
These textures are created in drawRect: and only need to be rendered once in the life of the view (with the possible exception of optionally recolouring them).
However, the slide is a little jerky, aI assume this is because the texture keeps being redrawn.
I was wondering if there is any value in rendering the texture by directly using the views CALayer. Will this avoid the texture being re rendered whenever the view moves?
I have never used CALayer before.
thanks
karl

If you override drawRect and use your view like that, it'll be redraw every time. What you can do is create a method that draw your view once and that is being called once (let's say after init) and don't override drawRect. That should fix your performance issue.
One more thing, every view are backed by a layer (CALayer), if you do animation and you want better performance you should move the layer instead of moving his view (UIView) during animation. To access the layer of a view, just do myView.layer.
Hope this was helpful.

Related

Swift: Drawing a UIBezierPath based on touch in a UIView

I've been looking at this thread as I'm trying to implement the same thing. However, I see that the Canvas class is implemented as a subclass of UIImageView. I'm trying to do the same thing except in a UIView. How will using a UIView rather than UIImageView affect the implementation of this solution? I see self.image used a couple times, but I don't know how I'd change that since I don't think that is available in a generic UIView.
Yes, you can implement this as a UIView subclass. Your model should hold the locations of the touch events (or the paths constructed from those locations) and then the drawRect of the view can render these paths. Or you create CAShapeLayer objects associated with those paths, too. Both approaches work fine.
Note, there is some merit to the approach of making snapshots (saved as UIImage) objects that you either show in a UIImageView or manually draw in drawRect of your UIView subclass. As your drawings get more and more complicated, you'll start to suffer performance issues if your drawRect has to redraw all of path segments (it can become thousands of locations surprisingly quickly because there are a lot of touches associated with a single screen gesture) upon every touch.
IMHO, I think that other answer you reference goes too far, making a new snapshot upon every touchesMoved. When you look at full resolution image for retina iPad or iPhone 6 plus, that's a large image snapshot to create upon every touch event. I personally adopt a hybrid approach: My drawRect or CAShapeLayer will render the current path associated with the current gesture (or the collection of touchesMoved events between touchesBegan and touchesEnded), but when the gesture finishes, it will create a new snapshot.
In the answer to that question, self.image is drawn into the drawing context first, then drawing is applied on top, then finally the image is updated to be the old image with new content drawn on top.
Since you just want to add a UIBezierPath, I'd just create a CAShapeLayer into which you place your bezier path, and place it on top of your views backing layer (self.view.layer). There's no need to do anything with DrawRect.

When is the right time to animate and add sublayers in an UIView subclass

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.

How to control what part of a UIView to update (iOS/Xamarin) - I need to prevent the screen from being fully erased

I am working on a graphing application which takes in a stream of data and displays it. It turns out that drawing a lot of line segments in UIView (Core Graphics) is a very slow. I'm trying to speed it up by only drawing a portion of the screen that has new data.
I currently use this function to redraw the screen (using Xamarin/monotouch)
NSTimer.CreateRepeatingScheduledTimer (TimeSpan.FromSeconds (0.1), delegate {
pane.SetNeedsDisplay ();
});
where pane is my UIView object. The pane object knows itself what part of the screen needs to be redrawn. I think there's another method that specifies a region of the screen to redraw - is that the right way to go? Or some other solution?
Also, the code draws a set of grids. These never change. Is there a way to save them to some layer or something so that I can just add it to the background?
The alternative is to go with openGL though drawing lines doesn't look as good and I'll have to implement a lot of extra code to make it look right.
Thanks!
For the graph data, you should be using CoreGraphics directly to render the lines in your UIView subclass' drawRect: method. Make sure that method only redraws the data that overlaps with the CGRect argument. The method setNeedsDisplay marks the entire UIView as needing a redraw, so instead of using that when your graph data changes, you should use the UIView's method setNeedsDisplayInRect: to indicate that there's new data in that region of the UIView that will call drawRect: to be called, which will only redraw that portion (if you've implemented it right).
Regarding the background grid, you can render the grid to a UIImage then display that UIImage behind your view that draws the graph. You'll want to look at the documentation for UIGraphicsBeginImageContext, CALayer renderInContext:, UIGraphicsGetImageFromCurrentImageContext, and UIGraphicsEndImageContext.
You can use this variant:
void SetNeedsDisplayInRect (RectangleF rect);
That will only request that the specified region be redrawn.

CALayer & drawRect

In View and Window Architecture is stated, quote:
Views work in conjunction with Core Animation layers to handle the rendering and animating of a view’s content. Every view in UIKit is backed by a layer object (usually an instance of the CALayer class), which manages the backing store for the view and handles view-related animations.
Farther in the "The View Drawing Cycle" section is stated:
The UIView class uses an on-demand drawing model for presenting content. When a view first appears on the screen, the system asks it to draw its content. The system captures a snapshot of this content and uses that snapshot as the view’s visual representation.
Does that mean, that the content drawn in a view in its drawRect method call, is captured in a snapshot in saved in its backing core animation layer?
If not, where do this content snapshot "reside"?
If not, does that mean that CALayer is used to render "static" content, content that doesn't change very often, and drawRect is used to render content that changes often, for example in a game app?
p.s.
The questions are not related to any particular code implementation.
I just want to understand the ios view-layer architecture.
Does that mean, that the content drawn in a view in its drawRect method call, is captured in a snapshot in saved in its backing core animation layer?
Yes. Everything uses layers under the hood. UIView's -drawRect will capture what you draw and (as far as I know) set the content on a sublayer of the view. It might even do this on the main layer object. That's where the 'snapshot' is saved.
If not, does that mean that CALayer is used to render "static" content,
content that doesn't change very often, and drawRect is used to render
content that changes often, for example in a game app?
How often the content changes doesn't really affect the choice. There is not much difference in using drawRect vs. manually creating CALayers. It depends on how you want to organize the sub-elements in your views, or if you want to create low level reusable layer objects without the details of UIView (e.g. CATextLayer). If you have various different sub-elements to draw then you may split them into different layers with their own custom drawing code. If you just have one simple piece of content to draw, you can do that all in a single drawRect implementation.
Having said this, you do need to be aware that each layer will end up being a separate GPU "element", so there can be performance benefits to reducing the number of layers you have, or using the shouldRasterize+rasterizationScale properties of a parent layer. This will take a snapshot of an entire layer hierarchy and create a single rasterized image to be rendered instead of n separate ones.
Does that mean, that the content drawn in a view in its drawRect method call, is captured in a snapshot in saved in its backing core animation layer?
Two words: "implementation details"
If not, does that mean that CALayer is used to render "static" content, content that doesn't change very often, and drawRect is used to render content that changes often, for example in a game app?
Not exactly. Layers are very good at animating content (as hinted by the framework name Core Animation). drawRect is good for advanced drawing but can be to slow to redraw every frame (obviously depending on what your are drawing).
I didn't see any mention of the Core Animation Programming Guide in your question. It is a good place to learn more about the layer part of views.
Every UIView has an underlying CALayer (which is what actually gets rendered on the screen).
A CALayer is just a bitmap (holds pixels). When you call setNeedsDisplay to your view the CALayer gets marked for redrawing. At the end of the run loop, after events are processed, a CGContextRef gets created and the drawRect delegate gets called. You then draw things to the created context which then gets copied into the bitmap and ultimately composited with other layers to be displayed on the screen.
So yes, the "snapshot" is stored in the CALayer. This is just an optimization so that the layers don't have to redraw themselves unless they're flagged to be redrawn (with setNeedsDisplay).

Using Quartz for small graphic project

I'm developing a iOS project that involves drawing small graphics (lines and paths) on the screen.
I initially chose to use Quartz instead of OpenGL, because I need to display some basic shapes and I need to update them every 5 seconds, so I thought Quartz was better and easier.
I found out that I can't simply draw in a view, but I have to subclass a UIView and draw in the drawRect method.
In my project, the user should be able to pinch and zoom on graphics, so I planned to add a pinchgesture to the view, but I am doubtful about how to redraw everything after the pinch. Do I have to erase everything and re-add the subviews so the drawRect will trigger or is there a better way to do this?
Thanks a lot.
When using Quartz, you technically don't have to subclass the view and replace the drawRect, but it probably is best practice. When you want to redraw your window, just call [self setNeedsDisplay]; (if calling from the subclassed view, or [self.view setNeedsDisplay]; if doing it from the view controller). This will result in calling your drawRect method for you and it takes care of everything for you.
See the setNeedsDisplay documentation for more information.

Resources