{"id":483,"date":"2021-07-14T18:36:00","date_gmt":"2021-07-14T16:36:00","guid":{"rendered":"https:\/\/mic.st\/blog\/?p=483"},"modified":"2023-05-15T21:29:36","modified_gmt":"2023-05-15T19:29:36","slug":"how-to-animate-uibezierpath-using-cabasicanimation","status":"publish","type":"post","link":"https:\/\/mic.st\/blog\/how-to-animate-uibezierpath-using-cabasicanimation\/","title":{"rendered":"Animate UIBezierPath using CABasicAnimation"},"content":{"rendered":"\n<p>Currently, I am working on an app that features a table view with custom cells representing single items or grouped items. Y<mark class=\"annotation-text annotation-text-yoast\" id=\"annotation-text-d32e943a-6b3b-4b1a-8bba-a02e0940a145\"><\/mark>ou can expand the group cells (to see their content) by selecting them. There are multiple ways to achieve this, I decided to use a custom <code>UIViewController<\/code> transition in that I animate a <code>UIBezierPath<\/code> with <code>CABasicAnimation<\/code>. During the custom transition, the layer drawn by the animated <code>UIBezierPath<\/code> is layed on top of the item. This makes it look as if the actual cell is expanding. After finally getting this animation done, I decided to write a post about it. The final view can be reused for a lot of different animations or can be at least a good starting point. So, in this post I will show you how to animate <code>UIBezierPath<\/code> using <code>CABasicAnimation<\/code> because even easy animations can be tricky.<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-d0b3c9c8 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\"><\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-video\"><video controls src=\"https:\/\/mic.st\/blog\/wp-content\/uploads\/2021\/07\/Screen-Recording-2021-07-14-at-08.49.16-1.mov\"><\/video><figcaption class=\"wp-element-caption\">The animation we want to create.<\/figcaption><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\"><\/div>\n<\/div>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>If you are not sure what a b\u00e9zier curve actually is you can check out wikipedia for a more graphical reference how this works (<a href=\"https:\/\/en.wikipedia.org\/wiki\/B%C3%A9zier_curve\" target=\"_blank\" rel=\"noopener\">https:\/\/en.wikipedia.org\/wiki\/B%C3%A9zier_curve<\/a>) or have a look at Apple&#8217;s documentation (<a href=\"https:\/\/developer.apple.com\/documentation\/uikit\/uibezierpath\" target=\"_blank\" rel=\"noopener\">https:\/\/developer.apple.com\/documentation\/uikit\/uibezierpath<\/a>). However, there might be a chance that you already have used b\u00e9zier curves in some graphical tool (e.g. Photoshop, Sketch, etc). I think it&#8217;s easiest to understand what happens if you already dragged around these curves in some tool.<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">As easy as creating two paths<\/h2>\n\n\n\n<p>Unfortunately, <code>CABasicAnimation<\/code> is not as easy as using <code>UIView.animate()<\/code>. However, if you master this more sophisticated way of animating you can do pretty cool stuff that you cannot do with just using <code>UIView.animate()<\/code>. Because of the API being not so straight forward I use this animation presented here as a kind of template in different places. I reused it and just changed <code>initialPath<\/code> and <code>finalPath<\/code> to create this one.<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-d0b3c9c8 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\"><\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-video\"><video controls src=\"https:\/\/mic.st\/blog\/wp-content\/uploads\/2021\/07\/Screen-Recording-2021-07-14-at-17.42.49.mov\"><\/video><figcaption class=\"wp-element-caption\">Another animation based on mostly the same code.<\/figcaption><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\"><\/div>\n<\/div>\n\n\n\n<p>For me there there is one really important rule when it comes to animating <code>UIBezierPath<\/code>:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>The starting <code>UIBezierPath<\/code> and the final <code>UIBezierPath<\/code> should have the same number of control points. If not, your animation will most probably not look as you would expect. <\/p>\n<\/blockquote>\n\n\n\n<p>I will show you later why this is important. First let me briefly describe what we want to do: <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>There is an initial <code>UIBezierPath<\/code> that will hold the control points to draw the content of the first frame of our animation.<\/li>\n\n\n\n<li>A final <code>UIBezierPath<\/code> will hold the control points for the ending frame of the animation. <\/li>\n\n\n\n<li>We also want to reverse the animation somehow or play it from the final to the inital path<\/li>\n\n\n\n<li>There should be a completion handler for the forward and the reversed animation so that we can easily chain other actions.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">The Code<\/h2>\n\n\n\n<p>Alright, let&#8217;s write the code! There are so many pitfalls when using <code>CABasicAnimation<\/code>, so I think it is best to fiddle around with the animation in a fresh new <code>.playground<\/code> file.<\/p>\n\n\n\n<p><em>(You can download the complete code at the end of this post)<\/em>.<\/p>\n\n\n\n<p>The starting file will look like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"Swift\" data-shcb-language-slug=\"swift\"><span><code class=\"hljs language-swift shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> UIKit\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> PlaygroundSupport\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/\/ A view with an animated layer, that expands a colored layer with the view's bounds to a given `CGRect`.<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">AnimatedCardView<\/span>: <span class=\"hljs-title\">UIView<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/ The colored card shape.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">let<\/span> shapeLayer = <span class=\"hljs-type\">CAShapeLayer<\/span>()\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/ The alpha mask, needed so that the animation does not clip.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">let<\/span> maskLayer = <span class=\"hljs-type\">CAShapeLayer<\/span>()\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/ The initial path at the animation's beginning<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">var<\/span> initialPath = <span class=\"hljs-type\">UIBezierPath<\/span>()\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/ The final path at the animation's end.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">var<\/span> finalPath = <span class=\"hljs-type\">UIBezierPath<\/span>()\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/ The layer's background color.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">var<\/span> layerBackgroundColor: <span class=\"hljs-type\">UIColor<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/ The initializer.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/ - Parameters:<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/   - frame: The view's frame.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/   - cornerRadius: The layer's corner radius.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/   - backgroundColor: The layer's solid color.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-keyword\">init<\/span>(frame: <span class=\"hljs-type\">CGRect<\/span>, cornerRadius: <span class=\"hljs-type\">CGFloat<\/span>, backgroundColor: <span class=\"hljs-type\">UIColor<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-keyword\">self<\/span>.layerBackgroundColor = backgroundColor\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-keyword\">super<\/span>.<span class=\"hljs-keyword\">init<\/span>(frame: .<span class=\"hljs-keyword\">init<\/span>(x: <span class=\"hljs-number\">0<\/span>, y: <span class=\"hljs-number\">0<\/span>, width: frame.width, height: frame.height))\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-keyword\">self<\/span>.backgroundColor = .clear\n<\/span><\/span><span class='shcb-loc'><span>\t\tinitialPath = <span class=\"hljs-type\">UIBezierPath<\/span>(roundedRect: frame, cornerRadius: cornerRadius)\n<\/span><\/span><span class='shcb-loc'><span>\t}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/ Animates the view's layer to the size of a given frame.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/ - Parameters:<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/   - frame: The final frame of the layer.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/   - duration: The animation's duration.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/   - completed: The animation's completion handler.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-function\"><span class=\"hljs-keyword\">func<\/span> <span class=\"hljs-title\">animate<\/span><span class=\"hljs-params\">(to frame: CGRect, duration: CFTimeInterval = <span class=\"hljs-number\">1<\/span>, completed: <span class=\"hljs-params\">(<span class=\"hljs-params\">()<\/span><\/span><\/span><\/span> -&gt; <span class=\"hljs-type\">Void<\/span>)?) {\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/ &#91;...]<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\t}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/ Animates the view's layer back to its initial bounds.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/ - Parameters:<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/  \t- frame: The initial frame of the reverted animation.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/   - duration: The animation's duration.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/   - completed: The animation's completion handler.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-function\"><span class=\"hljs-keyword\">func<\/span> <span class=\"hljs-title\">reverseAnimation<\/span><span class=\"hljs-params\">(from frame: CGRect, duration: CFTimeInterval = <span class=\"hljs-number\">1<\/span>, completed: <span class=\"hljs-params\">(<span class=\"hljs-params\">(Bool)<\/span><\/span><\/span><\/span> -&gt; <span class=\"hljs-type\">Void<\/span>)?) {\n<\/span><\/span><span class='shcb-loc'><span>\t\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/ &#91;...]<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t\n<\/span><\/span><span class='shcb-loc'><span>\t}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ &#91;...]<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Swift<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">swift<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>I created a new class that subclasses <code>UIView<\/code> to make this animation easily reusable in some other view or view controller. We also have two methods that are in charge for animating the path to its final or initial state. You could also combine both of these methods into one but for simplicity: Let&#8217;s keep them separated for now.<\/p>\n\n\n\n<p>The <code>CGRect<\/code> passed during initialization will be used as dimensions for the initial path, so we only need to pass in the final path when animating. We could also do this in the initializer but passing it when actually animating felt more flexible.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Setting up all the layers<\/h2>\n\n\n\n<p>Core Animation uses layers for rendering content. If you want to add animations you add sublayers with content you want to animate and add the animations to these layers. For our purpose I created <code>shapeLayer<\/code> on that the visible shape will be animated and an additional <code>maskLayer<\/code> that will hold information about which parts will be visible. Both layers will contain the same path. First I tried to create the animation without setting <code>mask<\/code> but that did not work me. Without setting it, the animation always looked clipped.<\/p>\n\n\n\n<p>All of this can be setup in <code>layoutSubviews<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Swift\" data-shcb-language-slug=\"swift\"><span><code class=\"hljs language-swift\"><span class=\"hljs-keyword\">override<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">func<\/span> <span class=\"hljs-title\">layoutSubviews<\/span><span class=\"hljs-params\">()<\/span><\/span> {\n    <span class=\"hljs-keyword\">super<\/span>.layoutSubviews()\n    shapeLayer.path = initialPath.cgPath\n    layer.addSublayer(shapeLayer)\n    maskLayer.path = shapeLayer.path\n    maskLayer.position =  shapeLayer.position\n    shapeLayer.fillColor = <span class=\"hljs-type\">UIColor<\/span>.red.cgColor.\n    layer.mask = maskLayer\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Swift<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">swift<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Setting the final path<\/h2>\n\n\n\n<p>Before we write some actual animation code, let me show you how to setup the final path and why it is important that your starting path and your final path have the same number of control points.<\/p>\n\n\n\n<p>I introduced another method called <code>setFinalPath(frame:)<\/code> that saves the passed final path in a property so that the reverse animation can access it as well.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Swift\" data-shcb-language-slug=\"swift\"><span><code class=\"hljs language-swift shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/\/ Sets the final path of the animation.<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/\/ - Parameter frame: The `CGRect` used to create the path.<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">func<\/span> <span class=\"hljs-title\">setFinalPath<\/span><span class=\"hljs-params\">(frame: CGRect)<\/span><\/span> {\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-keyword\">if<\/span> <span class=\"hljs-keyword\">let<\/span> superview = superview {\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-keyword\">let<\/span> frame = superview.convert(frame, to: <span class=\"hljs-keyword\">self<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>\t\tfinalPath = <span class=\"hljs-type\">UIBezierPath<\/span>(roundedRect: frame, cornerRadius: .leastNonzeroMagnitude)\n<\/span><\/span><span class='shcb-loc'><span>\t}\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Swift<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">swift<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>CGRect<\/code> that is used to create the final <code>UIBezierPath<\/code> is converted to the superview&#8217;s coordinate system before initializing the path (see line 5). This ensures that the layer will be presented at the right position, independent from the <code>AnimatedCardView<\/code>&#8216;s dimensions.<\/p>\n\n\n\n<p>Also have a look at the initializer for the path (see line 6). I did create a rounded rectangle with the smallest possible value for <code>cornerRadius<\/code> so that there are no visible corners. If we would use <code>UIBezierPath(rect:)<\/code> which also creates a rectangle without any rounded corners we would get a strange looking animation (see following video). This is because both paths have differnt numbers of control points.<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-d0b3c9c8 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\"><\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-video\"><video height=\"301\" style=\"aspect-ratio: 207 \/ 301;\" width=\"207\" controls src=\"https:\/\/mic.st\/blog\/wp-content\/uploads\/2021\/07\/Screen-Recording-2021-07-14-at-17.27.51.mov\"><\/video><figcaption class=\"wp-element-caption\">Different numbers of control points lead to strange animations.<\/figcaption><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\"><\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Forward animation (expand rectangle)<\/h2>\n\n\n\n<p>We will have a look now at the first animation. So, without further talking, here is the code:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"Swift\" data-shcb-language-slug=\"swift\"><span><code class=\"hljs language-swift shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/ Animates the view's layer to the size of a given frame.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/ - Parameters:<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/   - frame: The final frame of the layer.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/   - duration: The animation's duration.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/\/   - completed: The animation's completion handler.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-function\"><span class=\"hljs-keyword\">func<\/span> <span class=\"hljs-title\">animate<\/span><span class=\"hljs-params\">(to frame: CGRect, duration: CFTimeInterval = <span class=\"hljs-number\">1<\/span>, completed: <span class=\"hljs-params\">(<span class=\"hljs-params\">(Bool)<\/span><\/span><\/span><\/span> -&gt; <span class=\"hljs-type\">Void<\/span>)?) {\n<\/span><\/span><span class='shcb-loc'><span>\t\tsetFinalPath(frame: frame)\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-type\">CATransaction<\/span>.begin()\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-type\">CATransaction<\/span>.setCompletionBlock {\n<\/span><\/span><span class='shcb-loc'><span>\t\t\tcompleted?(<span class=\"hljs-literal\">true<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>\t\t}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-comment\">\/\/ Do animation stuff<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-type\">CATransaction<\/span>.commit()\n<\/span><\/span><span class='shcb-loc'><span>\t}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Swift<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">swift<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>When using Core Animation, you create a <code>CABasicAnimation<\/code> for that you define which values are changed and what timing function to use while animating. You add this animation after configuring it to a layer. If you want to group multiple animations you can use <code>CAAnimationGroup<\/code>. You can read more about this here: <a href=\"https:\/\/developer.apple.com\/documentation\/quartzcore\/caanimationgroup\" target=\"_blank\" rel=\"noopener\">https:\/\/developer.apple.com\/documentation\/quartzcore\/caanimationgroup<\/a>.<\/p>\n\n\n\n<p>This list of calls to configure the animation will be nested between <code>CATransaction.begin<\/code> and <code>CATransation.commit<\/code> (see line 8 and 15). If you want to add a completion block, you can do that by adding <code>CATransation.setCompletionBlock { }<\/code>. Core Animation calls the content of this completion block after finishing the animation.<\/p>\n\n\n\n<p>Let&#8217;s have a look now at the actual animation code:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"Swift\" data-shcb-language-slug=\"swift\"><span><code class=\"hljs language-swift shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">let<\/span> animation = <span class=\"hljs-type\">CABasicAnimation<\/span>(keyPath: <span class=\"hljs-string\">\"path\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>animation.duration = duration\n<\/span><\/span><span class='shcb-loc'><span>animation.fromValue = initialPath.cgPath\n<\/span><\/span><span class='shcb-loc'><span>animation.toValue = finalPath.cgPath\n<\/span><\/span><span class='shcb-loc'><span>animation.timingFunction = <span class=\"hljs-type\">CAMediaTimingFunction<\/span>(name: .<span class=\"hljs-keyword\">default<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>animation.fillMode = .forwards\n<\/span><\/span><span class='shcb-loc'><span>animation.isRemovedOnCompletion = <span class=\"hljs-literal\">false<\/span>\n<\/span><\/span><span class='shcb-loc'><span>shapeLayer.add(animation, forKey: <span class=\"hljs-string\">\"animateCard\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>maskLayer.add(animation, forKey: <span class=\"hljs-string\">\"animateCard\"<\/span>)\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Swift<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">swift<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>First you have to initialize an instance of <code>CABasicAnimation<\/code> with a named key path. After that, you configure your animation. The most important settings are the animation&#8217;s <code>duration<\/code>, its initial value (<code>fromValue<\/code>) and its final value (<code>toValue<\/code>). Furthermore, you set which timing function to use (e.g. start fast and end slow or a linear function) and which state of the animation should be visible at the end (<code>fillMode<\/code>). We do not want to remove the animation after finishing. Therefore we set <code>isRemovedOnCompletion<\/code> to <code>false<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The reversed animation (collapse rectangle)<\/h2>\n\n\n\n<p>The method for running the reversed animation is pretty similar. Therefore, I just start with the complete listing here:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"Swift\" data-shcb-language-slug=\"swift\"><span><code class=\"hljs language-swift\"><span class=\"hljs-comment\">\/\/\/ Animates the view's layer back to its initial bounds.<\/span>\n<span class=\"hljs-comment\">\/\/\/ - Parameters:<\/span>\n<span class=\"hljs-comment\">\/\/\/  \t- frame: The initial frame of the reverted animation.<\/span>\n<span class=\"hljs-comment\">\/\/\/   - duration: The animation's duration.<\/span>\n<span class=\"hljs-comment\">\/\/\/   - completed: The animation's completion handler.<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">func<\/span> <span class=\"hljs-title\">reverseAnimation<\/span><span class=\"hljs-params\">(from frame: CGRect, duration: CFTimeInterval = <span class=\"hljs-number\">1<\/span>, completed: <span class=\"hljs-params\">(<span class=\"hljs-params\">(Bool)<\/span><\/span><\/span><\/span> -&gt; <span class=\"hljs-type\">Void<\/span>)?) {\n\tsetFinalPath(frame: frame)\n\t<span class=\"hljs-type\">CATransaction<\/span>.begin()\n\t<span class=\"hljs-type\">CATransaction<\/span>.setCompletionBlock {\n\t\tcompleted?(<span class=\"hljs-literal\">true<\/span>)\n\t}\n\t<span class=\"hljs-keyword\">let<\/span> reverseAnimation = <span class=\"hljs-type\">CABasicAnimation<\/span>(keyPath: <span class=\"hljs-string\">\"path\"<\/span>)\n\treverseAnimation.duration = duration\n\treverseAnimation.fromValue = finalPath.cgPath\n\treverseAnimation.toValue = initialPath.cgPath\n\treverseAnimation.timingFunction = <span class=\"hljs-type\">CAMediaTimingFunction<\/span>(name: .<span class=\"hljs-keyword\">default<\/span>)\n\treverseAnimation.isRemovedOnCompletion = <span class=\"hljs-literal\">false<\/span>\n\treverseAnimation.fillMode = .backwards\n\tshapeLayer.removeAnimation(forKey: <span class=\"hljs-string\">\"animateCard\"<\/span>)\n\tmaskLayer.removeAnimation(forKey: <span class=\"hljs-string\">\"animateCard\"<\/span>)\n\tshapeLayer.add(reverseAnimation, forKey: <span class=\"hljs-string\">\"animateCard\"<\/span>)\n\tmaskLayer.add(reverseAnimation, forKey: <span class=\"hljs-string\">\"animateCard\"<\/span>)\n\t<span class=\"hljs-type\">CATransaction<\/span>.commit()\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Swift<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">swift<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>As you can see, the code is almost the same. So, it might be a good candidate for refactoring, e.g. add a parameter <code>direction<\/code> or something like that. However, for simplicity reasons, we just have two separate methods for now. <\/p>\n\n\n\n<p>The main differences between both methods are that <code>fromValue<\/code> and <code>toValue<\/code> are flipped and we also set the <code>fillMode<\/code> to <code>.backwards<\/code>. To not conflict with the forward animation, we remove it and add the reversed animation at the end. We use the same key for both directions because we never want to have both animations at the same time.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>In my opinion Core Animation code does not read very difficult but writing it can sometimes lead to headaches. However, if you stick to the rule of having the same amount of control points at the beginning and end of the animation, you can create pretty cool stuff in a short amount of time.<\/p>\n\n\n\n<p>If you want to download the playground file, you can get it here: <\/p>\n\n\n\n<div class=\"wp-block-file\"><a id=\"wp-block-file--media-a69bcd2b-8ca2-47b6-80a9-afa9d6f371ff\" href=\"https:\/\/mic.st\/blog\/wp-content\/uploads\/2021\/07\/CoreAnimation.playground.zip\">CoreAnimation.playground<\/a><a href=\"https:\/\/mic.st\/blog\/wp-content\/uploads\/2021\/07\/CoreAnimation.playground.zip\" class=\"wp-block-file__button wp-element-button\" aria-describedby=\"wp-block-file--media-a69bcd2b-8ca2-47b6-80a9-afa9d6f371ff\" download>Download<\/a><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Currently, I am working on an app that features a table view with custom cells representing single items or grouped items. You can expand the group cells (to see their content) by selecting them. There are multiple ways to achieve this, I decided to use a custom UIViewController transition in that I animate a UIBezierPath&hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[3],"tags":[37,4,35],"class_list":["post-483","post","type-post","status-publish","format-standard","hentry","category-ios-development","tag-animations","tag-ios","tag-swift"],"_links":{"self":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts\/483","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/comments?post=483"}],"version-history":[{"count":42,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts\/483\/revisions"}],"predecessor-version":[{"id":729,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts\/483\/revisions\/729"}],"wp:attachment":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/media?parent=483"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/categories?post=483"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/tags?post=483"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}