📜 ⬆️ ⬇️

How convenient can an API be for drawing in iOS?

Hello, my name is Victor, I work for Exyte. Recently, we posted in open source our internal development - a library for working with vector graphics and its animation Macaw . I want to share my impressions of using it in a real project and tell about its advantages over the native API.


As developers, we often have to create non-standard controls and repeat the same routine actions even for simple effects:



Let's try to create a custom control and use it as an example:


ControlImage



let circlePath1 = UIBezierPath( arcCenter: center, radius: r, startAngle: offset, endAngle: offset + CGFloat(angle), clockwise: true) let circlePath2 = UIBezierPath( arcCenter: center, radius: r, startAngle: offset, endAngle: offset + CGFloat(angle), clockwise: false) UIColor.init(colorLiteralRed: 0.784, green: 0.784, blue: 0.784, alpha: 1.0).setStroke() UIColor.clear.setFill() circlePath1.lineWidth = 10.0 circlePath1.stroke() UIColor.white.setStroke() circlePath2.lineWidth = 10.0 circlePath2.stroke() 

We decided to get rid of this routine and created Macaw. With it, we can describe the scene above in a simple, functional style:


 let circle = Circle(cx: Double(center.x), cy: Double(center.y), r: r) self.node = [ circle.arc(shift: -1.0 * M_PI_2, extent: angle) .stroke(fill: Color.rgb(r: 200, g: 200, b: 200), width: 10.0), circle.arc(shift: -1.0 * M_PI_2 + angle, extent: 2 * M_PI - angle) .stroke(fill: Color.white, width: 10.0) ].group() 

Controls


Left: Core Graphics, right: Macaw


As you can see, the scene is represented as a model, which can be easily reused or modified. For example, using the Group node, you can create large hierarchies of objects with relative characteristics (position, transparency, etc.)


 let sceneNode = [ shape1, shape2, ... [ subshape1, subshape2 .... [...].group() ].group(place: Transform.move(dx: 10.0, dy: 10.0).scale(sx: 0.5, sy: 0.5), opacity: 0.9) ].group() 

It's hard to even imagine how much effort it would take to create such a scene using the “clean” Core Graphics API. To use this beauty, simply inherit from MacawView, or use MacawView as a container. After that, the "magic" out of the box will begin, for example, model changes by machine will automatically redraw the content.


But to create a beautiful effect, you need one more thing - animation. Using Core Graphics, we have two ways:



 let scaleTransform = CGAffineTransform.init(scaleX: 0.1, y: 0.1) let scaleAnimation = CABasicAnimation(keyPath: "transform") scaleAnimation.toValue = CATransform3DMakeAffineTransform(scaleTransform) scaleAnimation.duration = 1.0 scaleAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) scaleAnimation.autoreverses = true self.layer.add(scaleAnimation, forKey: "scale_animation") 


Using Macaw, this animation is much nicer:


 let scaleAnimation = self.node.placeVar.animation(to: GeomUtils.centerScale(node: self.node, sx: 0.1, sy: 0.1), during: 2.0).easing(.easeOut) scaleAnimation.autoreversed().play() 

Animations


Yes, that's all the code. Is there an overhead compared to "pure" Core Graphics? Under the hood, Macaw uses CAKeyframeAnimation, and it takes a little time (depending on the complexity of the model) to calculate the animation frames. Otherwise, these are the same Core Graphics challenges.


Well, what about animation for scene content? Is it possible to animate the state of the model objects or animatedly replace the entire model tree? Unfortunately, there is no way to somehow optimize this process, the only solution is to redraw the entire model manually. The good news is that Macaw has a very convenient API for this.


Let's refactor our control code so that it is easier to create a tree of objects:


 contentNode(angle: Double) -> [Node] { ... let circle = Circle(cx: Double(center.x), cy: Double(center.y), r: r) let text = Text(text: "\(value)", font: Font(name: "System", size: 38), fill: Color.white) let textCenter = GeomUtils.center(node: text) text.place = Transform.move(dx: Double(center.x) - textCenter.x, dy: Double(center.y) - textCenter.y) return [ text, circle.arc(shift: -1.0 * M_PI_2, extent: angle) .stroke(fill: Color.rgb(r: 200, g: 200, b: 200), width: 10.0), circle.arc(shift: -1.0 * M_PI_2 + angle, extent: 2 * M_PI - angle) .stroke(fill: Color.white, width: 10.0) ] } ... self.node = contentNode(angle: angle).group() 

Now we can create content animations, replacing the content of the subtree inside the model every frame:


 guard let rootNode = self.node as? Group else { return } rootNode.contentsVar.animate({ t -> [Node] in return self.contentNode(angle: 2 * M_PI * t) }, during: 2.0) 

Content animation


Even if the scene is complex, only the area of ​​the subtree we are changing will be redrawn during the animation.


As you can see, using Macaw, we can achieve the performance of pure Core Graphics, but with more readable and easier supported code. Much remains not analyzed, but I hope that this review will inspire you to use our library in your project. We are constantly working on improvements and will be happy for your advice and assistance.


')

Source: https://habr.com/ru/post/318684/


All Articles