Replicating the App Store download button
With the release of iOS 11, Apple introduced a completely redesigned App Store app with greatly improved UI. Among many features and improvements, one thing that I found interesting was the redesigned download/purchase/update button.
I’ve been working in my spare time on implementing the download button. After a bit of experimenting, I implemented a working version. I only needed a couple more days to clean up the code and package it into a CocoaPod. You can take a look at the source code on the AHDownloadButton GitHub repo. The final result can be seen below:
The README section describes how to use the library. In this blog post, I will provide some details on how I implemented the download button as well as discuss some interesting points about the implementation.
Download state implementation
The download button has 4 different states:
Each of these states is implemented as a separate view class.
downloaded state use the same class -
HighlightableRoundedButton. It is a simple subclass of
UIButton that has rounded corners and highlightable background and title. To implement highlighting I’ve overridden the
isHighlighted property of
pending state uses a
CircleView. It is a
UIView subclass that has
endAngleRadians properties that define the start and end angle of the circle. The drawing is implemented by adding a
CAShapeLayer with a circular path. The path is defined by overriding
downloading state is presented using the
ProgressButton class. It is a
UIControl subclass that uses a
ProgressCircleView to show download progress. Whenever the user updates the progress property of the
ProgressCircleView animates the progress change. The progress property is implemented in the following way:
Whenever the progress is set to a value lower than 1, the
isAnimating property is checked to make sure that the last change is not currently animating. If it’s not, it will animate the progress change to the newest value. If the progress is set to 1 and the last change is still animating then the running animation is overridden and progress is animated from the current presentation value to 1.
This is the code that animates the progress change:
State transition animations
Transition from one state to another is animated whenever the
state property of the download button is changed. The initial implementation was very simple:
After a bit of testing, I noticed an edge case that I missed here. What would happen if the state is changed during an animation of the previous state change? The animation for the latest state update would override the previous animation and it wouldn’t look nice. I needed to make sure that, whenever the state is changed, the animation for previous change completes before I animate the latest change.
To do that, I used a background
DispatchQueue in combination with a
There are a few things to go over here:
I used a background queue to chain one transition animation after another. I am doing this because I do not want to block the main thread while waiting for the previous animation to finish. Also, note that I’ve captured the value of the
stateproperty inside a capture list. I have to do that because I want the closure to capture the
statevalue at the time of creation, not at the time of execution.
I used a
DispatchGroupfor synchronization. I call
animationDispatchGroup.enter()to indicate that I am going to animate a transition and that other animations need to wait for the current one to finish.
If the transition happens from
downloadedstate with a complete progress, then a delay has to be introduced to let the final progress animation finish before the transition animation starts.
Since animations need to be done on the main thread I have to dispatch the animation on the main queue.
In the last step, I call
animationDispatchGroup.wait(). This will cause the background queue to be blocked until the
animateTransition(from: oldValue, to: currentState)finishes execution and calls
Using this approach I have synchronized the animations and I didn’t block the main thread while doing that. To see how
animateTransition(from: oldValue, to: currentState) works I suggest that you take a look at the source code.
This was an interesting and fun project to make. In this post, I’ve described the more interesting parts of the implementation. If you want to know how I implemented it in more detail, how I separated and organized the classes and how I used them, head over to my GitHub repo. You can also find the documentation that describes how you can use and customize the download button.