Cashapelayer to implement a circular picture loading animation
A few weeks ago, Michael Villar created a very interesting loading animation in the motion experiment.
The GIF image below shows this loading animation, which combines a circular progress indicator with a circular fade animation. The effect of this combination is interesting, unique and somewhat fascinating.
This tutorial will teach you how to recreate this effect using swift and core Animatoin. Let's get started!
Basis
First download the boot project for this tutorial, then compile and run it. After a while, you should see a simple image display:
This startup project has been pre-programmed with views and loading logic in the right place. Take a minute to browse to get a quick look at the project; there is a viewcontroller,viewcontroller named Customimageview in the Uiimageview subclass, There is also a sdwebimage method that is called to load the image.
You may notice that when you first run the app, the app appears to pause for a few seconds when the image is downloaded, and then the image is displayed on the screen. Of course, there is no circular progress indicator at the moment – you will create it in this tutorial!
You will create this animation in two steps:
Circular progress. First, you'll draw a circular progress indicator and update it based on the download progress.
Expands the circle picture. Second, you will reveal the downloaded image by extending the circular window.
Follow the steps below to step up the implementation!
Create a circular indicator
Think about the basic design of the progress indicator. The indicator starts out empty to show 0% progress, and then gradually fills up until the image finishes downloading. It is fairly straightforward to implement the path for circle by setting Cashapelayer.
Note: If you are unfamiliar with the basic concepts of cashapelayer (or calayers), you can view the Scott Gardner Calayer in IOS with Swift article.
You can control the appearance of the start and end positions by Cashapelayer the Strokestart and Strokeend properties. By changing the value of the strokeend between 0 and 1, you can properly populate the download progress.
Let's try. Create a new file with the file name Circularloaderview with the Ios\source\cocoa Touch Class template. Set it to a subclass of UIView.
Click Next and create. The new subclass UIView the code that will be used to save the animation.
Open Circularloaderview.swift and add the following properties and constants to this class:
1 Let Circlepathlayer =220.0
Circlepathlayer represents this circular path, and Circleradius represents the radius of the circular path.
Add the following initialization code to Circularloaderview.swift to configure the shape layer:
1 OverrideInit (frame:cgrect) {2 super.init (frame:frame)3 Configure ()4 }5 6 Required Init (coder Adecoder:nscoder) {7 super.init (Coder:adecoder)8 Configure ()9 }Ten One func Configure () { ACirclepathlayer.frame =bounds -Circlepathlayer.linewidth =2 -Circlepathlayer.fillcolor =Uicolor.clearcolor (). Cgcolor theCirclepathlayer.strokecolor =Uicolor.redcolor (). Cgcolor - Layer.addsublayer (Circlepathlayer) -BackgroundColor =Uicolor.whitecolor () -}
Two initialization methods call the Configure method, and the Configure method sets the line width of a shape layer to 2,fill color to Clear,stroke color to red. Add Circlepathlayer to the View ' main layer. Then set the view's backgroundcolor to white, and when the image loads, the rest of the screen is ignored.
Add path
You will notice that you have not assigned a path to the layer. To do this, add the following method (or in the Circularloaderview.swift file):
1 func circleframe () cgrect {2 0022* Circleradius)3 circleframe.origin.x = Cgrectgetmidx (circlepathlayer.bounds)- Cgrectgetmidx (circleframe)4 circleframe.origin.y = Cgrectgetmidy (circlepathlayer.bounds)- Cgrectgetmidy (circleframe) 5 return Circleframe 6 }
The previous method returns an instance of CGRect that defines the path to the indicator. This border is 2*circleradius wide and 2*circleradius high, placed in the center of the view.
Each time the size of this view changes, you will need to recalculate the circleframe, so you might put it in a separate method.
Now add the following methods to create your path:
1 func circlepath ()23return45 }
This is just the uibezierpath that returns the circle according to the circleframe limit. Since Circleframe () returns a square, in this case the ellipse will eventually become a circle.
Since layers does not autoresizingmask this attribute, you need to update the Circlepathlayer frame in the Layoutsubviews method to properly respond to the size change of the view.
Next, overwrite the Layoutsubviews () method:
1 Override func layoutsubviews () {2 super.layoutsubviews ()3 Circlepathlayer.frame = bounds4 circlepathlayer.path = circlepath (). Cgpath5 }
As you change the frame, you call the Circlepath () method here to trigger the recalculation of the path.
Now open the Customimageview.swift file and add the following Circularloaderview instance as a property:
1 Let Progressindicatorview = Circularloaderview (Frame:cgrectzero)
Next, add these lines of code to the init (coder:) method before you download the image code:
12 progressindicatorview.frame =3 Progressindicatorview.autoresizingmask =. Flexiblewidth |. Flexibleheight
The above code adds a progress indicator as a subview to the custom image view. Autoresizingmask ensure that the progress indicator view remains the same as the size of the image view.
Compile and run your project; you'll see a red, hollow circle appear, like this:
OK – you already have a progress indicator drawn on the screen. Your next task is to stroke according to the changes in the download schedule.
Modify Stroke length
Go back to the Circularloaderview.swift file and add the following code directly to the other properties in this file:
1 var progress:cgfloat {2 Get {3 returnCirclepathlayer.strokeend4 }5 Set {6 if(NewValue >1) {7Circlepathlayer.strokeend =18}Else if(NewValue <0) {9Circlepathlayer.strokeend =0Ten}Else { OneCirclepathlayer.strokeend =NewValue A } - } -}
The above code creates a computed property– that is a property without any back-variables – it has a custom setter and getter. This getter just returns circlepathlayer.strokeend,setter verify that the input values are between 0 and 1, and then properly set the layer's Strokeend property.
At the first run, add the following line of code to configure () to initialize the progress:
Progress = 0
Compile and run the project; you should see nothing but a blank screen. Believe me, this is good news. Setting progress to 0, which in turn sets Strokeend to 0, means that the shape layer has nothing to draw.
The only thing left to do is your indicator update progress in the image download callback method.
Go back to the Customimageview.swift file and use the following code instead of the comment update progress here:
1 self!. progressindicatorview.progress = CGFloat (receivedsize)/cgfloat (expectedsize)
This is mainly calculated by dividing the receivedsize by expectedsize to calculate the progress.
Note: You will notice that block uses the weak self reference – this avoids retain cycle.
Compile and run your project; you'll see the progress indicator start moving like this:
Even if you do not add any animated code yourself, Calayer easily discovers any animatable property on the layer and animate smoothly when the property changes.
The first stage has been completed. Now enter the second and final stages.
Creating reveal animations
The reveal stage displays the image in window and then gradually expands the shape of the circle ring. If you have already read the previous tutorial, the tutorial focuses on creating a ping-style view controller animation, you will know that this is a good use case for the Calayer mask property.
Add the following methods to the Circularloaderview.swift file:
1 func reveal () {2 //13BackgroundColor =Uicolor.clearcolor ()4Progress =15 //26Circlepathlayer.removeanimationforkey ("Strokeend")7 //38 Circlepathlayer.removefromsuperlayer ()9Superview?. Layer.mask =CirclepathlayerTen}
This is a very important way to understand, let's go through the paragraph:
Set the background color of the view to clear, then the image behind the view is no longer hidden, then set progress to 1 or 100%.
Use the Strokeend property to remove any pending implicit animations, otherwise interfere with reveal animation. For more information about implicit animations, see iOS animations by tutorials.
Remove Circlepathlayer from its superlayer, then assign to Superview layer Maks, and with circular mask "hole", the image is visible. This allows you to reuse the existing layer and avoid repeating the code.
Now you need to call reveal () somewhere. In the Customimageview.swift file, replace the following code with the reveal image here Comment:
1 self!. Progressindicatorview.reveal ()
Compile and run your app; Once the image starts to download, you'll see a small ring in the display.
You can see your image– in the background, but there's almost nothing!
Expansion ring
Your next step is to extend this ring inside and outside. You can do this with two separate, concentric uibezierpath, but you can do it in a more efficient way, just using a Bézier path.
How to do it? You just increase the radius of the Circle (path property) to scale out, while increasing the line width (linewidth property) to make the ring thicker and inward-expanding. Eventually, the entire image is displayed below when the two values grow to enough.
Go back to the Circularloaderview.swift file and add the following code to the end of the Reveal () method:
1 //12Let center =Cgpoint (X:cgrectgetmidx (bounds), y:cgrectgetmidy (bounds))3Let Finalradius = sqrt ((center.x*center.x) + (center.y*center.y))4Let Radiusinset = Finalradius-Circleradius5Let Outerrect = Cgrectinset (Circleframe (),-radiusinset,-radiusinset)6Let Topath =Uibezierpath (ovalinrect:outerrect). Cgpath7 8 //29Let Frompath =Circlepathlayer.pathTenLet Fromlinewidth =Circlepathlayer.linewidth One A //3 - Catransaction.begin () - Catransaction.setvalue (kcfbooleantrue, forkey:kcatransactiondisableactions) theCirclepathlayer.linewidth =2*Finalradius -Circlepathlayer.path =Topath - Catransaction.commit () - + //4 -Let linewidthanimation = Cabasicanimation (keypath:"linewidth") +Linewidthanimation.fromvalue =Fromlinewidth ALinewidthanimation.tovalue =2*Finalradius atLet pathanimation = Cabasicanimation (keypath:"Path") -Pathanimation.fromvalue =Frompath -Pathanimation.tovalue =Topath - - //5 -Let groupanimation =Caanimationgroup () inGroupanimation.duration =1 -Groupanimation.timingfunction =camediatimingfunction (name:kcamediatimingfunctioneaseineaseout) toGroupanimation.animations =[Pathanimation, Linewidthanimation] +Groupanimation.Delegate= Self -Circlepathlayer.addanimation (Groupanimation, Forkey:"Strokewidth")
Now segment by paragraph to explain exactly what the above code is doing:
Once the radius of the circle is determined, the image view can be completely restricted. Then calculate the cgrect to completely limit the circle. The Topath represents the final shape of the Cashapelayer mask.
Sets the linewidth and path initial values to match the value of the current layer.
Set the final value of linewidth and path, which prevents them from jumping back to their original values when the animation is complete. Catransaction sets the value of Kcatransactiondisableactions to True to disable the implicit animations of the layer.
Create an instance of two cabasicanimation, one is the path animation, one is the LineWidth animation, the linewidth must increase to twice times as fast as the radius growth rate, so that the circular inward expansion is the same as the outward expansion.
Add two animations to a caanimationgroup, and then add animation group to layer. Assign self to delegate, and then you will use it.
Compile and run your project; you will see that once the image has finished downloading, reveal animation will pop up. But even if the reveal animation is complete, some of the circles will remain on the screen.
In order to fix this situation, add the following implementation animationdidstop (_:finished:) To Circularloaderview.swift:
1override func animationdidstop (anim:caanimation! 2 Superview?. Layer.mask =3 }
The code removes the mask from the super layer, which completely removes the circle.
Compile and run your project again, and you'll see the effect of the whole animation:
Congratulations, you've finished creating a circular image loading animation!
Next
You can download the entire project here.
Based on this tutorial, you can further refine your animation's time, curves, and colors to suit your needs and personal design aesthetics. One possible improvement is to set the LineCap property value of the shape layer to kcalinecapround to round the tail of the circular progress indicator. Think about what else you can improve on your own.
If you like this tutorial and would like to learn how to create more animations like this, check out Marin Todorov's book iOS animations by tutorials. It starts with a basic animation and then steps through layer animations, animating constraints, view controller transitions and more
If you have any questions or comments about this tutorial, please participate in the discussion below. I'd love to see you add such cool animations to your app.
iOS development-Graphical programming Swift Chapter &cashapelayer realization of circular picture loading animation