0

I'm working on a drawing app where every bezier path is CAShapelayer and I'm adding these sub-layers on super layer UIView(CALayer), once the points/lines exceed a certain threshold eg: 1000 CAShapelayer then the drawing, zoom, and scroll lags, is their a way to optimize this?

Raj Kiran
  • 152
  • 1
  • 9
  • The obvious answer would be "don't draw a thousand layers". Why do you need that many? Why can't you just draw what you need in a "collapsed" single layer? – Mike 'Pomax' Kamermans Jun 07 '22 at 23:33
  • @Mike'Pomax'Kamermans these 1000's layers are hand-drawn and I cannot restrict a user on this. Think about it if a user wants to write an essay each character will be an individual cashapelayer likewise, an essay can easily cross 1000 layers and what do you mean by "collapsed" layer??? – Raj Kiran Jun 09 '22 at 08:41
  • Why on earth would each letter be its own cashapelayer? You just need one, and then you add each new trace to that layer's path. Even the example on https://developer.apple.com/documentation/quartzcore/cashapelayer does that. – Mike 'Pomax' Kamermans Jun 09 '22 at 15:27
  • @Mike'Pomax'Kamermans the writer uses several colors in an essay, now as per the example in the apple docs if each letter is added as subpath of single shapelayer then the entire essay will have only one colour, user can’t change the colour for individual characters and hence when you use separate shapelayers every character can have its own colour. – Raj Kiran Jun 10 '22 at 16:42
  • Okay? Can you explain what you are actually making where people would switch color a thousand times in a way that you can't just put paths in the appropriate layer for that color? Just because a user writes in black, then red, then black again, then red again, almost never needs four layers, it just needs two. The only time you need extra layers is if the user draws on top of previous content and you need a new layer to force correct z-ordering. – Mike 'Pomax' Kamermans Jun 10 '22 at 16:50
  • Consider this app as a notebook if a user draws something let's say a diagram it's obvious he will use many colors. Why are there so many colors is my client's and their user's concern or requirement. Is it possible to reduce the lag when it crosses 1000's sublayers is my concern...that's the answer I'm looking for... I have already tried all the reference links which you shared... – Raj Kiran Jun 13 '22 at 13:14

1 Answers1

1

Couple options for trying to use thousands of layers...

First, I ran a test on a 3rd-gen iPad Pro, generating 8100 shape layers. While there was a little bit of "lag" when zoomed-out to see the full view, it certainly did't make it unusable... and I notice little to no lag when zoomed in.

Second, instead of using shape layers, you could define your own "layer" struct - tracking path, fill, border, etc. Then override draw() and only draw the paths where their bounding box intersects the draw rect.

Third, instead of using a thousands-of-layers view in your scroll view, use maybe an image view. Each time you add a new layer, draw that layer to the image in the image view. As you zoom it will become fuzzy... so each time the user ends zooming, update the image at the new scale. You'll notice a slight lag as the fuzzy image becomes clear, but that will only happen at the end of the zoom. You could even alleviate that by using "stepped" zooming - such, 100%, 200%, 400%, 800%.


Edit

I put together an Example app that:

  • generates 95 paths, using the Glyphs for chars "!" through "~" from Times New Roman font
  • paths have min 4 points, max 115 points; min 0 curves, max 55 curves
  • we add 33,805 CAShapeLayer (not text) layers, using 6 fill/stroke color combinations, to a 3508 x 2480 view in a scroll view

On an old iPhone 7 running iOS 13.3 ... sure, it has a "little" lag, but not what I would call unusable.

Looks like this at 1.0 Zoom Scale:

enter image description here

You may want to take a look at it and see if it has the same "lag" you're experiencing - https://github.com/DonMag/ShapeLayersWork


Edit 2 - 8137 layers using your hand-drawn "a" path:

enter image description here


Edit 3

  • "Chalkduster" font
  • generate a "grid" to fill the 3508 x 2480 view
  • cycle through paths
  • put all paths of the same color on the same layer (so 6 layers)

Here's the output:

enter image description here

It took over 20-seconds for the view to become visible, and, as we would expect, it's completely unusable.

The "Points: / Curves:" lines list the number of points and curves per layer -- 4-million points and almost 2-million curves. I really think you're going to need to re-think your whole approach.

As a side note... are you familiar with the Sketch App for Mac? I put some text on some layers, using Chalkduster... converted the layers to outlines (paths instead of text)... and even with a small number of layers Sketch performance gets bad.

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Thanks Don, could you please explain the 2nd case in detail, and in the 3rd case if you are telling me to draw on the image then it's more like flattening the layer...right? – Raj Kiran Jun 14 '22 at 11:51
  • @RajKiran - *2nd case*... search for `swift coregraphics` and start reading. Too complex for an answer here. *3rd case*... yes, essentially flattening the layers. I'm curious - when you talk about "1000s of layers" and "each character will be an individual cashapelayer" ... are those realistic cases? Would a user write an entire essay, manually placing / sizing / coloring each and every character one-at-a-time? – DonMag Jun 14 '22 at 15:51
  • so just to give you clarity, take an example of Apple notes we have made a similar app, the problem with Apple notes is it is a raster-based and hence it will pixelate when you zoom/generate pdf whereas the engine which I made is vector-based, any level of zooming will not pixelate the characters and this app is provided to k-12 hence these colour requirements and in the first stage of the project I limited to only 5 colours and it was easy to manage because I created a single layer for each colour but requirements changed and hence the issue. Thanks, mate – Raj Kiran Jun 15 '22 at 12:06
  • @RajKiran - are you converting text to bezier paths? If so, have you considered using `CATextLayer` for text and `CAShapeLayer` for "drawing"? – DonMag Jun 15 '22 at 12:14
  • No actually it's completely a handwritten-based app. – Raj Kiran Jun 15 '22 at 12:23
  • @RajKiran -- hmm... I wonder if you're doing something out of the ordinary? Just how big of a view are you using? And how far are you allowing it to zoom? In a quick test, using a 1600x1600 view, adding 6135 shape layers with filled+bordered paths... running on an old iPhone 7... and I see very little "lag" – DonMag Jun 15 '22 at 17:34
  • firstly the UIVIew size is 3508 x 2480(close to A4 or letter size), secondly, the zoom level is up to 10 times(max)....thirdly the most important issue which I noticed is if you add more than 5000 layers which have a proper shape like a square(use the native function to generate square) then there is no lag but the lag is noticeable only when the bezier path is freeform that is handwritten, basically what I'm saying is the number of points in a freeform path is more than a standard shape Square(4 points), I think the lag is due to more no of points. – Raj Kiran Jun 15 '22 at 18:54
  • @RajKiran - take a look at the **Edit** to my answer. – DonMag Jun 15 '22 at 21:53
  • I tried your code works pretty smooth even for 30000 layers but when I tried your code with a custom path for the letter "a", even with 1000 layers there was a lag in the zoom, I have forked your code, and modified it, in Viewcontroller you can find my comment "play with below 2 lines" you will understand the issue and on a note, I did try flattening process but was not good user experience when layers flattened while writing https://github.com/Rajkiran93/ShapeLayersWork/tree/RK – Raj Kiran Jun 16 '22 at 18:19
  • OK - checking your modification... You have generated 1000 layers with *exactly overlapping* paths. That's what's causing the drop in performance. I made a change to spread them around in a grid, generating 8137 layers, with no performance problem. Worth noting: I generated my grid and then *also* 1000 layers in the same place -- definite "lag" when zooming on ***that area***. When scrolling that 1000-layer-overlap out of view, zooming performance is back to normal. I understand trying to plan for "everything" - but, is that a realistic possibility? – DonMag Jun 16 '22 at 20:31
  • for a quick test I added the overlapping layers, ideally, the user wouldn't do that... my bad, but when these layers were spread across the UIView(my algo in my project) like the typical essay I faced the lag, have you updated the changes on git, can I try it? – Raj Kiran Jun 17 '22 at 07:38
  • also when I changed the font style from "Times new roman" to "Chalkduster" which has more points and curves I noticed the zoom was lagging even at 2000 layers...the handwritten letter "a" had fewer points and curves compared to the new font style "Chalkduster". I think I can confirm that this lag is due to the number of points and curves with respect to the "Visible device window", i.e., If the view is completely zoomed out the widow has to render more layers compared to when it is zoomed in where it works smoother...hope you got my point. – Raj Kiran Jun 17 '22 at 10:57
  • @RajKiran - don't know if Stack Overflow alerts you to answer edits, but I added an **Edit 3**. – DonMag Jun 17 '22 at 21:16
  • @RajKiran - also, just for kicks, I tried something with Sketch... using "A0" paper size (3370x2384), I added the 4 paragraphs from https://www.lipsum.com homepage, set font to Chalkduster at 42-point so it filled most of the page - about 3,000 characters. Converted to Outlines, which took about a minute and Activity Monitor showed **45 GB** of RAM used. Mom use drops after the conversion, but I wouldn't want to try to work with the result. – DonMag Jun 17 '22 at 21:18
  • I output it to PDF, and the PDF is painfully slow to navigate / zoom / etc, and is clearly rasterizing and re-rasterizing in the process. There are a number of vector-editing iOS apps out there. Would be interesting to try this with one of those... – DonMag Jun 17 '22 at 21:19
  • thank you for taking your time out for me, yeah I'm not being notified for Edits, I tried the sketch like you said it's laggy, the first approach which I took when I started this project was using Pencilkit even after writing full-page there was no lag or whatsoever, but then the issue was strokes were pixelated or not sharp enough, then I migrated to Pdfkit which is a pain in the a$$ because even 10 lines of an essay I could see the lag and app crashed every time I zoomed in then I exported it to mac and opened it with preview app then I realized the issue was with Pdfkit – Raj Kiran Jun 18 '22 at 11:04
  • Finally I'm using this layer-based approach, as of now I'm flattening after every 500 layers, the only issue is with the User experience where the user can see a small flickering while flattening, the last thing which I want to try is Metalkit but the learning curve is huge and fewer examples to try and understand... – Raj Kiran Jun 18 '22 at 11:09
  • @RajKiran - sounds like a reasonable approach. Wonder if "flattening" every 100 layers might get rid of the flickering? Or, maybe pop up a message -- something like "Optimizing..." to let the user know? – DonMag Jun 18 '22 at 11:25
  • the popup and flickering will interrupt the user while writing or drawing... Ideally I want the optimisation to happen in the background to give uninterrupted user experience... So im optimising as much as I can untill I find a better approach – Raj Kiran Jun 18 '22 at 14:28
  • I happened to see this conversation when working on an update to my drawing app. So far my app uses multiple views, which are called layers to the user. Drawing is done using drawRect on a scratch layer. As soon as a curve is finished being drawn, it is copied to the active user layer. The scratch layer is performant because only the curve being drawn is update. The other layers are updated only after each curve is drawn. Now I want to add gradient fills, which means I need probably a CAShapeLayer for each curve. – Victor Engel Aug 30 '23 at 13:27
  • Unlike the previous discussion where multiple curves of the same color can be combined. With a drawing app that uses gradients, each gradient fill will have its own gradient and needs to clip to the path, so I see no other way than to use separate CALayers, if it is to be a vector drawing. – Victor Engel Aug 30 '23 at 13:29
  • One other suggestion to the OP would be to explore a tiled design. – Victor Engel Aug 30 '23 at 13:30
  • @VictorEngel - if you're curious, take a look at this: https://stackoverflow.com/a/75899517/6257435 -- it demonstrates drawing many vector paths instead of using many layers. I just updated it to include drawing gradient fills in paths. Full example project here: https://github.com/DonMag/VirtualZoom – DonMag Aug 30 '23 at 16:22
  • I'll look at it when next I have both power and internet. Internet is down now and power is promised to be down tomorrow for "hazardous tree trimming". At a glance I saw gradient mentioned only in the github project. I'll take a closer look in a couple days. – Victor Engel Aug 30 '23 at 21:04
  • I took a look at your github project. Drawing into a context is how I did gradients at first, but I found it worked only for the first gradient. I see you have a resetClip function when I was not using. That's probably all I needed. – Victor Engel Aug 30 '23 at 21:12