Using Quartz for small graphic project - ios

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.

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.

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

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.

Animated MKOverlayView

I've run across a couple of similar questions here regarding getting an animated MKOverlayView working property with decent performance (e.g., an animated radar overlay). However, while the answers have helped lead me in the right direction, I still don't fully grasp what I'm missing yet.
I've been trying to get this UIImageView method working, which simply adds an UIImageView as a subview of the MKOverlayView, then adds the necessary images in the animation sequence to its animationImages property. This doesn't seem to work for me as the UIImageView and its associated images are never rendered. I've even tried calling setNeedsDisplay on the overlay view which also doesn't help. In my instance, the images used in the animation have to be loaded from a remote server first and are updated fairly frequently.
There's also this method that suggests using cocos2D to setup an animated sprite, which I'd like to avoid if the above method with UIImageView works successfully.
I've been struggling with this for two days straight now, so there's likely something I'm totally missing? Do I need to use Core Graphics to do the drawing similar to how it's performed in the main drawMapRect:zoomScale:inContext: method (which I've also tried but didn't work)?
Maybe you can try this. Subclass the MKCircleView and animate a UIImageView added on it with Core Animation. It works well with the map moving around and scale with the map zoom in and out.
http://yickhong-ios.blogspot.com/2012/04/animated-circle-on-mkmapview.html

Resources