The best way to implement drawing features like Keynote - iphone

I'm trying to make a little iPad tool's for drawing simple geometrical objects (rect, rounded rect, ellipse, star, ...).
My goal is to make something very close to Keynote (drawing feature), i.e. let the user add a rect (for instance), resizing it and moving it. I want too the user can select many objects and move them together.
I've thought about at least 3 differents ways to do that :
Extends UIView for each object type, a class for Rect, another for Ellipse, ... With custom drawing method. Then add this view as subview of the global view.
Extends CALayer for each object type, a class for Rect, another for Ellipse, ... With custom drawing method. Then add this layer as sublayer of the global view layer's.
Extends NSObject for each object type, a class for Rect, another for Ellipse, ... With just a drawing method which will get as argument a CGContext and a Rect and draw directly the form in it. Those methods will be called by the drawing method of the global view.
I'm aware that the two first ways come with functions to detect touch on each object, to add easily shadows,... but I'm afraid that they are a little too heavy ? That's why I thought about the last way, which it seems to be straight forward.
Which way will be the more efficient ??? Or maybe I didn't thought another way ?
Any help will be appreciated ;-)
Thanks.

I'd use the UIKit classes to do your drawing, then profile and optimise your code from there.
Apple/iPad info: link text

My first feeling was to make the 3rd way, but to be convinced, just after I've posted my message, I did some tests with just a global view and over 200 geometrical forms (Rect, Rounded Rect and Ellipse) on it and I move only a half with touchMoved event. I did this test with the way 1 (Subclassing UIView) and the way 3 (Subclassing NSObject), the way 2 seems to me too restrictive and not help me at all.
The resuslt is that the way 1 seems to be more efficient... There is no lag when I move 60 objects together ! Moreover using this way would probably help me because using view comes which some interesting functions like touch detection on complex path (see UIBezierPath), object hierarchy handled by the UIView classe...
So I will use that way and come back here to share my results ;-)
Regards

It's better to use CGLayer objects. The benefits are:
It's much faster and more memory efficient. For simple objects, adding a view is much more expensive and complicates the view hierarchy. For complicated objects, the caching done on CGLayers can boost performance.
It's easy to group objects together. you just put everything in a new layer, and voila! There's almost no overhead.
Using CGLayer and other Quartz objects gives you a lot more flexibility.
The only drawback is that you have to directly use Quartz 2D. It's not really difficult, but needs some learning if you haven't used it before.

CAShapeLayer pretty much handles your option 2. It does rect and rounded rect (see cornerRadius) by default, or you can give it a path for any arbitrary shape. For your option 1, you can use a CAShapeLayer with a UIView instead of implementing drawRect and it may be faster.

Related

In SwiftUI, how to draw shadows with high performance?

I use the .shadow(color:, radius:, x:, y:) to draw shadows in my application. This is the only way I know of drawing apps in SwiftUI. I use the .sheet(isPresented:, content:) method to pop up a view, which contains a lot of shadows, and when I debug view hierarchy, I saw these warnings:
But I don't know how to setting shadowPath, or pre-rendering the shadow into an image and putting it under the layer in SwiftUI, please help me.
This warning is not caused because your code is inherently bad, but as a way of telling you that there are much more performant ways of rendering shadows.
As your UI elements are currently written, SwiftUI is drawing the shadows around your view objects dynamically (at Runtime) based on wherever their positions and bounds are at the time, and that rendering will follow the view throughout it's lifecycle.
It's a math intensive process and involves many draw-calls to the GPU in the best of cases, and CPU bottlenecking as well in the worst of cases.
There are several different ways of rendering shadows in Swift. Most of them utilize frameworks OUTSIDE of SwiftUI (UIKit and CoreGraphics, usually, though Metal, Core Animation, and Core Image have been important in various applications.)
This warning is probably not a big deal if you're not seeing performance problems in the UI layer on target hardware, but if you're very motivated to solve the problem, there are some options:
Option 1
The absolute easiest thing to do if you just want to make the error go away would be to force a GPU call rasterization for the view + shadow by adding
.drawingGroup()
somewhere after the .shadow view. Be advised, this will likely look like crap compared to dynamic shadows. If you're familiar with UIKit, this is similar to the layer.shouldRasterize property on UIView.
Option 2
Speaking of UIKit, an alternative would be to head over there and use either a UIViewRepresentable of your SwiftUI drawing logic, or a completely separate UIView. Either way:
myView = UIView()
myView.layer.shadowPath = UIBezierPath(rect: myView.bounds.cgPath)
should get you started... the other shadow properties and stuff will help.
Option 3
You could render the shadow as an image, either programatically (hard) or in an imaging editing application (annoying) and load is as an image at a lower Z index than your view, and scale them to give the illusion of depth.
This is the kind of hacky work around that game developers used to do when they had crappy hardware but still wanted things to look good.
In the end... for MOST SwiftUI views this warning likely can be ignored. If you load the code in Instruments, you'll likely see that the dynamic rendering of drop shadows under a View is probably not impacting your view rendering performance significantly. This warning is only usually visible inside a UI Debug session.
Hope this helps set you on the path to a solution.
I ran into this this week. And I think I figured out a way that is as performant as the shadowPath option in UIKit. I think that when you use Shape().shadow(...) it draws an efficient shadow based on the path. So IF you know the shape of the thing you're giving a shadow to, you can do it like this:
content
// Use background so the shadow is the same size as the content
.background(
// I'm assuming rectangle but it can be anything like with rounded corners too
Rectangle()
// Add efficient shadow
.shadow()
// Add an inset so you don't see the ugly inner edge of the shadow, it will be under your content
.padding(1)
)
This was much much faster than what I had before!

iPhone: draw the objects along the curve

Good day, friends.
There is a task: to draw repeating objects (UIImageView and UILabel) along the curve (if more exactly, it's an arc).
What classes should be used for it?
As an alternative to Android's Path, you can use either UIBezierPath or CGPath. The former is Objective-C, the later pure C. I recommend going with UIBezierPath as it's easier to use.
To rotate a view, use the transform property of UIView (see this question for an example on how to use it). But note that you then need to ignore the frame (it's undefined) and need to move the view by modifying the center instead.

Most efficient way for partially drawing an UIView in drawRect:

I'm using setNeedsDisplayInRect: as much as possible in my UIView subclass, but I don't know what to do in drawRect:. What is the best way to detect which parts of the UIView have to be drawn? Right now I've divided my UIView into several CGRect's. For each CGRect I'm calling CGRectContainsRect() to check whether that CGRect needs to be drawn or not. Is that the correct way, or is there a better way?
You'll want to draw any rect that is even partially in the invalidated rect, so you'll want to use CGRectIntersectsRect(). Having said that, I've seen few cases where it makes sense to partially draw UIViews -- unless you have a very large single view inside of a scroll view, you may as well draw the whole thing.
You're looking at a classic spatial partitioning task. Depending on your view complexity, you can use different strategies to find out what needs to be drawn. If your scene is very simple, then drawing everything or partitioning the scene into discrete areas and doing your bounding rectangle check is fine. For very complex scenes, there are several good data structures (such as octrees) for representing your scene as a tree, and performing spacial queries for objects within a given region.
Check out the topic of Spacial Partitioning on Wikipedia: http://en.wikipedia.org/wiki/Space_partitioning

Performance issues scaling multiple CALayers

I have two CALayer subclasses, each with their own drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx delegate. They are both simple layers (some single color shapes drawn with CG paths), but I need to scale about 12 instances simultaneously, and I'm having some issues with frame rates. I marked all of the layers as opaque to try to free up some cycles, and have tried using implicit and explicit basic animations (on the bounds property itself), as well as assigning CA3DTransform matricies to the transform property.
Does anyone know of a good way to quickly resize objects while maintaining a good frame-rate?
This doesn't sound to be beyond the capabilities of the iPhone.
One solution might be to render them to an image and scale that? This is (more or less) what CoreAnimation would do. It sounds like you have a defect though - maybe you should post your code and people could look at it.
Where are you performing the redraw and what are you redrawing?
I agree with Roger.
Check how often your drawLayer:inContext: methods (or whatever you use to draw) are being called. A simple NSLog can accomplish that. If they are being called constantly, consider Roger's idea of rendering to an image and scaling that.
You will likely have to fire up the performance tools to find your bottleneck.

How do I use CALayer with the iPhone?

Currently, I have a UIView subclass that "stamps" a single 2px by 2px CGLayerRef across the screen, up to 160 x 240 times.
I currently animate this by moving the UIView "up" the screen 2 pixels (actually, a UIImageView) and then drawing the next "row".
Would using multiple CALayer layers speed up performance of rendering this animation?
Are there tutorials, sample applications or code snippets for use of CALayer with the iPhone SDK?
The reason I ask is that most of the code snippets I find that demonstrate simple examples of CALayer employ method calls that do not work with the iPhone SDK. I appreciate any advice or pointers.
Okay, well, if you want something that has some good examples of CA good that draws things like that and works on the phone, I recommend the GeekGameBoard code that Jens Aflke published (it is an improved version of some Apple demo code).
Based on what you are describing I think you are doing somthing way more complicated than it needs be. My impression is you want basically a static view that you are animating by shifting its position so that it is partially off screen. If you just need to set some static content in your drawRect going through layers is not going to be faster than just calling CGFillRect() with your color. After that you could just use implicit animations and the animator proxy on UIView to move the view. I suspect you could even get rid of the custom drawRect: implementation with a patterned UIColor, but I honestly have not benchmarked the difference between the two.
What CALayer methods are you seeing that don't work on iPhone? Aside from animation features tied to CoreImage I have not noticed much that is missing. The big thing you are likely to notice is that all views are layer backed (so you do not need to do anything special to use layers, you can just grab a UIView's layer through the layer accessors methos), and the coordinate system has a top left origin.
In any event, generally having more things is slower than having fewer things. If you are just repeating the same pattern over and over again you are likely to find the best performance is implementing a custom UIView/CALayer/UIColor that knows how to draw what you want, rather than placing visually identical layers or views next to each other.
Having said that, generally layers are lighter weight than views, so if you have a lot of separate elements that you need to keep logically separated you will find that moving to layers can be a win over using views.
You might want to look at -[UIColor initWithPatternImage:] depending on exactly what you are trying to do. If you are using this two pixel pattern as a background color you could just make a UIColor that draws it and set the background.
What CALayer methods are you seeing that don't work on iPhone?
As one example, I tried implementing the grid demo here, without much luck. It looks like CAConstraintLayoutManager and CAConstraint are not available in QuartzCore.h.
In another attempt, I tried a very simple, small 20x20 CALayer object as a sublayer of my UIView's layer property, but that didn't show up.
Right now, I have a custom UIView of which I override the drawRect method. In drawRect I grab a context and render two types of CGLayerRefs:
At "off" cells I draw the background color across the entire 320x480 canvas.
At "on" cells, I either draw a single CGLayerRef across a grid of 320x480 pixels (initialization) or across a 320x2 row (animation).
During animation, I make a UIImageView clip view from 320x478 pixels, and draw a single row. This "pushes" my bitmap up the screen two pixels at a time.
Basically, I'd like to test whether or not using CALayer will accomplish two things:
Make my rendering faster, if CALayer has less overhead than what I'm doing now
Make my animation smoother, by letting me transition a layer up the screen smoothly
Unfortunately, I can't seem to get a basic CALayer working at the moment, and haven't found a good chunk of sample code to look at and play with.