Replicating Spotify's Now Playing UI using Auto Layout

 

Table of contents

  1. Deconstructing Spotify's Now Playing UI
  2. Album Art Collection View
  3. Album Art Collection View Cell
  4. Play button
  5. Playback buttons and stack view
  6. Progress Slider
  7. Current result
  8. Set Data source and Delegate of Collection View
  9. Add Insets to Collection View
  10. Add Zoom Effect to the Collection View Cell when scrolled
  11. Add Fade effect to the Collection View Cell when scrolled
  12. Add Snap-to Nearest effect when user stops scrolling

 

Deconstructing Spotify's Now Playing UI

We will breakdown and analyze the Now Playing screen of Spotify app, and try to replicate it using Auto Layout.

 

The playback button icons used in this post are from Font Awesome, you can use fa2png.io to generate png images from the font awesome icons.

Here's the Now Playing screen of Spotify viewed in iPhone 8 screen size:

Spotify UI

 

The scrollable album art is a collection view, song name and artist name are labels, there's five button (shuffle, previous, play/pause, next, repeat), the song progress bar is a slider. The left two and right two buttons are grouped in stack views, this will be explained more later.

 

Spotify UI Analyze

 

The vertical distance of each UI elements are shown using the blue arrow. Feel free to tweak the value of the vertical constraint until it feels right for you.

 

Album Art Collection View

Let's say each album art is a collection view cell itself, each cell is a square , meaning its width is equal to height, this means a ratio of 1:1 .

Collection Cell

 

To make it simple, assume the collection view containing the cell has the same height as the cell itself, with same width but with added spacing on the left (34 pt) and right (34 pt). This means the collection view has an aspect ratio of 1:1 + 34 +34 = 1:1 + 68.

Collection View

 

 

The collection view has a top constraint to the top edge of safe area, leading constraint with value 0, trailing constraint with value 0. The leading and trailing constraint means that the collection view's width is equal to the screen width.

 

Since there are distances from the album art to the screen edge (left and right), and the album art is a square, the height of the collection view will be 68 pt lesser than its width.

 

We can set a ratio constraint on the collection view like this :

Aspect ratio constraint

 

Aspect Ratio 2

 

In the attribute inspector, set the scrolling direction of the collection view to horizontal.

Horizontal direction

 

Album Art Collection View Cell

After creating the collection view, drag a collection view cell into the collection view if there isn't one already :

Collection View Cell

 

Set the collection view cell's background color to "Clear color" so it will be transparent. This is needed when we do the fading effect of the album art image in the next part. We will also set a reuse identifier for the cell so we can reuse it in code in the next part.

Cell Attributes

 

After that, drag an image view into the cell and add constraints to make the image has the same size as the cell. Create a top, leading, trailing and bottom constraints with value 0 for the image view.

Cell Image Constraint

 

Next, set the imageview's image to your favorite album art picture! 😆

 

Play button

Say you are using the play button from Font Awesome like this :

Play button

 

To make it simple, we set a fixed width (74pt) and height (74pt) for the Play button and center it horizontally to the parent view.

 

To add a surrounding circle to the play button, we can use the cornerRadius and borderWidth property of the button's layer.

 

Hmmm, now the play button image seems too big and too close to the surrounding circle :

too big

 

We can adjust the Image Insets of the button in Size inspector tab to add padding to the image :

Image insets

 

The image insets means the spacing from the image to the button edge. With the above values, we will set 20 pt padding on top, left, bottom and right for the image inside button.

 

After adding insets, the play button looks better now with padding :

circled play button

 

Playback buttons and stack view

The play button is located in the center (using the align horizontal center constraint), and there's other buttons beside it. At first glance, using fixed leading / trailing constraint between each button seems to do the job :

Spacing between buttons

 

These constraints looks fine on an iPhone SE screen size, but when viewed on iPhone 8 plus, the left and right edge of the screen feels empty as it have too much blank space.

Extra spacing

 

To reduce the blank space, we will have to distribute the button evenly, meaning they occupy a certain proportion of the width of the screen size. As the screen width increase, they should occupy more width so that there wouldn't be a chunk of blank space.

 

To distribute button evenly, we can use a stack view and set its Distribution property to Fill Equally. Then we place placeholder views inside the stackview and each of the placeholder view has the same width. Next we place the button inside the placeholder view, and horizontally center the button to the placeholder view.

Stack view

 

Repeat this process for the left part (shuffle and previous buttons). Now you should have an evenly distributed buttons. But you might find the image on the button stretches, this is because the content mode of image inside a button is "Scale to Fill" by default. To resolve the stretching issue, we can set the content mode to "Aspect Fit" using code :

 

This is how the playback buttons looks like in iPhone SE and iPhone 8 plus using stack view:

Evenly distributed buttons

 

Progress Slider

A default slider looks like this:

default slider

 

You can customize the left bar color and right bar color with the Min Track and Max track color property.

minMax Color

 

As the default circle size is too large for the song progress slider, we can replace it with a smaller circle (the circle is called as "Thumb"). You will need to prepare an image containing a smaller circle for this.

 

If you have noticed, when you are selecting the slider in Spotify, the thumb become larger and has another layer of half-transparent circle around it.

selectedStateSlider

The easiest way to replicate this is to prepare an image for it. And set a different thumb image for the selected state.

 

Current result

Part 1 Result

It kinda looks good now! Just that the collection view cell doesn't have spacing and doesn't have the zooming / fade out effect when scrolled. We will add these effect into the collection view next.

 

Set Data source and Delegate of Collection View

Create a custom collection view cell type, let's name it as "CoverCollectionViewCell". Set the class of the cell in Storyboard to "CoverCollectionViewCell". Then connect the album art image view in the cell in Storyboard to create an UIImageView outlet.

 

Link the collection view into the view controller, and set data source and delegate (UICollectionViewDelegateFlowLayout).

 

Now we have a somewhat dynamic collection view, and the cell size are dynamic based on the collection view size / screen size!

 

Add Insets to Collection View

Our collection view currently looks like this :

Collection view no inset

 

The first collection view cell is not center aligned as it sticks to the left edge of the collection view. Same goes to the last cell as it sticks to the right edge. We will add some padding to the left and right of the collection view, so that when user scroll to the first / last cells, there will be some space on the left / right edge.

 

Remember the aspect ratio we set for the collection view in previous part? The cell height is equal to the collection view's height, cell width is equal to cell height, hence cell width is equal to the collection's view height. If you are confused about how the value of padding is calculated, here's some explanation :

 

 

After setting the left padding and right padding insets, when scrolling to the first / last cell, it looks good now :

left spacing

 

right spacing

 

Add Zoom Effect to the Collection View Cell when scrolled

The fun part! When scrolling the collection view, as the middle cell is being scrolled to the side, its size become smaller, and the neighbouring cell approaching middle will become larger.

 

One way to think about this is that the size of the cell become smaller when it become further from the center of the collection view. When the cell is in the center, it has its original size, meaning the scale is 1x. When the cell move to left / right, we make it smaller by multiplying its size with a smaller scale (eg: 0.9x). The value of scale is determined by the distance from the center of the cell to the center of the collection view.

 

distance visualization

 

 

If we used the above formula, we will get the scaling effect but the scale can become zero or less than zero when the distance from cell center to collection view center is larger than the collection view center X position value!

scale Smol

 

To solve this problem, we will multiply value of (distance / collection view center x) with a number smaller than 1 so that it doesn't get too big, making scale too small.

 

Let's multiply it with 0.105 (you can use any other number smaller than 1 if you like), we will modify the above calculation to this :

 

It looks better now:

scale Good

 

If you pay attention to the Spotify album art collection view, the album art doesn't immediately get shrinked when it is moving away from the center. There is a minimum scroll distance required before the album art get shrinked.

 

Notice that within a certain scrolling distance, the size of the album art doesn't change :

scroll fixed

 

We can implement the minimum scroll distance by adding a tolerance value and a if-check like this:

 

What this do is that it add 0.02 to the scale value, for example, let say on a certain scrolling distance, the scale is supposed to be 0.98, then a 0.02 is added to it, making the scale 1.0, thus on that distance, the album art still haven't shrink. When the album art is on the exact middle, the scale value would be 1.02, we then update it to 1.0 by using the if check so that it doesn't grow larger than 1.0.

 

Now it looks good, we can apply the calculation into code like this :

 

Since collection view is a subclass of scrollview, when you set collectionView.delegate = self in the viewDidLoad, you can use the scroll view delegate method on the collection view in the view controller.

 

Add Fade effect to the Collection View Cell when scrolled

Similar to the zoom effect, we want to dim the album art when it is being scrolled to the side. Since we already have the scale value, we can use it to indicate the alpha of the album art image view. (Alpha 1.0 is fully opaque, alpha 0.0 is fully invisible)

 

We can add the code to change the alpha of the image view after the cell.transform = CGAffineTransform line.

 

Since the value of scale is between 0.86 and 1.0 , using this as alpha value doesn't show much difference in transparency. We want the alpha value to between 0.25 and 1.0 . To transform the value range from 0.86 - 1.0 to 0.25 - 1.0, we can write a function to transform the range using some mathematical formula.

The album art collection view is looking good now! When an album art is moving towards the edge of screen, it will get dimmed.

alpha

 

The only thing left now is a "snap-to nearest" effect, meaning if user stop scrolling the collection view, it should snap to the nearest album art and putting it in the middle.

 

Add Snap-to Nearest effect when user stops scrolling

The logic of snap-to nearest is that when user stops scrolling, the collection view should auto scroll to the current largest sized cell, and put the cell in the middle. The method that will be called when user stop scrolling a scrollview is scrollViewDidEndDragging.

 

To find the largest sized cell, we will loop through all the current visible cells in the collection view, record down the largest width value and the index of the cell with the largest width value. Then we will call collectionView.scrollToItem(at: ) to instruct the collection view to scroll to that cell.

 

 

After adding the snap-to nearest effect, it will look like this when user releases finger during scroll:

snap to

 

Congratulation if you have made it this far, you have successfully replicated Spotify's Now Playing UI screen! 🎉