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.
Related
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.
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.
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.
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.
I am making an app where one function is the ability to place pre-made doodles on the page, They will have the ability to zoom the doodles in and out and place them where they wish. This is all for the iPad. Do I need to use open GL for this or is there a better / easier way? (New to iOS programming)
You should be able to achieve this using CALayers. You will need to need to add the QuartzCore framework for this to work. The idea would be to represent each doodle as a single CALayer. If your doodles are images, you can use the contents property to assign the doodle to the layer. You will need to assign a CGImageRef object which you can easily retrieve using CGImage property of a UIImage object.
You will need a view which will be your drawing board. Since you want to be able to move and alter the sizes of the doodles, you will have to attach a UIPanGestureRecognizer object for moving the layers and a UIPinchGestureRecognizer to zoom the doodles in and out. Since the recognizers can only be attached to a view and not layers, the non-trivial part when the gesture handlers are called will be identify which sublayer of the view are they manipulating. You can get the touches for the gestures using locationInView: for the pan gesture and locationOfTouch:inView: for the pinch gesture with the view argument being the view that the gesture is being done on which can be retrieved using gesture.view. Once you identify the layer in focus, you can use translationInView: for pan gesture to move the layer and use scale property of the pinch gesture to transform the layer.
While CALayer objects are lightweight objects, you could face problems when there are just too many of them. So stress test your application. Another roadblock is that images are usually memory hogs so you might not be able to get a lot of doodles in.