Yalantis: iOS, Android And Web App Development Company

Contact Display Switch Animation: The Transition From List View to Grid View

This piece by Yalantis iOS developer Roman Sherbakov and UX/UI designer Sergii Ganushchak originally appeared on Codeguru.

Every time we design screens that feature friend lists or contact lists, we face the problem of choosing between list view and grid view. Although list view usually provides more details about each user or contact, grid view allows more users or contacts to appear on the screen at the same time.

Sometimes, you can't say for sure which variant is best for a particular use case. That's why we designed a UI that allows users to switch between list and grid views on the fly and choose the most convenient display type for themselves.

Figure 1: Two ways to view an UI on a mobile device

In addition to usernames and profile pictures, list view also provides information about posts, comments, and likes. A list view can include any information you need while browsing your friends list.

Grid view displays only profile pictures and usernames. This lets us fit more profiles on one screen. Grid view is useful when you're looking for a specific user and don't need any additional information.

We created design mockups for both list and grid views using Sketch. As soon as the mockups were ready, I used Principle to create a smooth transition between the two display types. Note that the hamburger menu changes its appearance depending on which view is activated:

Contact Display Switch Animation Use Cases

You can use our Contact Display Switch for:

  • Social networking apps
  • Dating apps
  • Email clients
  • Any other app that features list of friends or contacts

Furthermore, the DisplaySwitcher component that we created based on the idea of Contact Display Switch animation is not limited to friends lists and contact lists; it can work with any other content. It's up to your imagination!

Developing a DisplaySwitcher Component

First, we'll tell you how you can use our DisplaySwitcher component in your own iOS project. Then, we'll look under the hood and see how the animated transition between two collection view layouts works.

How to Use It

To begin, you need to create two layouts—one for displaying a list and another for displaying a grid:

private lazy var listLayout = BaseLayout(staticCellHeight: listLayoutStaticCellHeight, nextLayoutStaticCellHeight: gridLayoutStaticCellHeight, layoutState: .ListLayoutState)

private lazy var gridLayout = BaseLayout(staticCellHeight: gridLayoutStaticCellHeight, nextLayoutStaticCellHeight: listLayoutStaticCellHeight, layoutState: .GridLayoutState)

Parameters:

  • staticCellHeight – the height of the current cell
  • nextLayoutStaticCellHeight – the height of the next layout’s cell
  • layoutState – the layout state (list or grid)

After the layouts are ready, you need to set the current layout for the collection view (in our case, that’s listLayout) and set the current layout using CollectionViewLayoutState enum:

collectionView.collectionViewLayout = listLayout
private var layoutState: CollectionViewLayoutState = .ListLayoutState

Next, override two required methods of the collection view datasource:

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell

And also override one method of the collection view delegate:

func collectionView(collectionView: UICollectionView, transitionLayoutForOldLayout fromLayout: UICollectionViewLayout, newLayout toLayout: UICollectionViewLayout) -> UICollectionViewTransitionLayout {
       let customTransitionLayout = TransitionLayout(currentLayout: fromLayout, nextLayout: toLayout)
       return customTransitionLayout
   }

At this point, return the TransitionLayout instance. This means that you are going to use a custom transition. You can find more info on this method here.

Finally, you must make layouts change for some events (like pressing a button) using the TransitionManager class instance:

 let transitionManager: TransitionManager
       if layoutState == .ListLayoutState {
           layoutState = .GridLayoutState
           transitionManager = TransitionManager(duration: animationDuration, collectionView: collectionView!, destinationLayout: gridLayout, layoutState: layoutState)
       } else {
           layoutState = .ListLayoutState
           transitionManager = TransitionManager(duration: animationDuration, collectionView: collectionView!, destinationLayout: listLayout, layoutState: layoutState)
       }
       transitionManager.startInteractiveTransition()



Parameters:

  • animationDuration – time duration of the transition
  • collectionView – current collection view
  • destinationLayout – the layout you’re switching to
  • layoutState – the state of the layout you’re switching to

That’s it! Now you know how to use our component!

Going under the hood

We use five classes to implement our DisplaySwitcher:

  • BaseLayout is a class that deals with building layouts and overrides the UICollectionViewLayout methods for calculations of the required сontentOffset when switching from one layout to another.

  • BaseLayoutAttributes is a class for adding custom attributes.

  • TransitionLayout is a class that defines the custom attributes.

  • TransitionManager is a class that uses TransitionLayout and deals with the transition between layouts according to preset time durations.

  • RotationButton is a custom class that inherits from UIButton, and is used for a button that animates transition between the layouts.

Let’s explore these classes in more detail.

BaseLayout

In the BaseLayout class, we use methods for building list and grid layouts. But what’s most interesting here is the сontentOffset calculation that should be defined after the transition to a new layout.

First, save the сontentOffset of the layout you are switching from:

 override func prepareForTransitionFromLayout(oldLayout: UICollectionViewLayout) {
       previousContentOffset = NSValue(CGPoint:collectionView!.contentOffset)  
       return super.prepareForTransitionFromLayout(oldLayout)

   }

 Then, calculate the сontentOffset for the new layout in the targetContentOffsetForProposedContentOffset method:

override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint) -> CGPoint {
       let previousContentOffsetPoint = previousContentOffset?.CGPointValue()
       let superContentOffset = super.targetContentOffsetForProposedContentOffset(proposedContentOffset)
       if let previousContentOffsetPoint = previousContentOffsetPoint {
           if previousContentOffsetPoint.y == 0 {
               return previousContentOffsetPoint
           }
           if layoutState == CollectionViewLayoutState.ListLayoutState {
               let offsetY = ceil(previousContentOffsetPoint.y + (staticCellHeight * previousContentOffsetPoint.y / nextLayoutStaticCellHeight) + cellPadding)
               return CGPoint(x: superContentOffset.x, y: offsetY)
           } else {
               let realOffsetY = ceil((previousContentOffsetPoint.y / nextLayoutStaticCellHeight * staticCellHeight / CGFloat(numberOfColumns)) - cellPadding)
               let offsetY = floor(realOffsetY / staticCellHeight) * staticCellHeight + cellPadding
return CGPoint(x: superContentOffset.x, y: offsetY)
           }
       }
       return superContentOffset
   }

And then clear value of variable in method finalizeLayoutTransition:

override func finalizeLayoutTransition() {
       previousContentOffset = nil
       super.finalizeLayoutTransition()
   }

BaseLayoutAttributes

In the BaseLayoutAttributes class, a few custom attributes are added:

var transitionProgress: CGFloat = 0.0
   var nextLayoutCellFrame = CGRectZero
   var layoutState: CollectionViewLayoutState = .ListLayoutState

transitionProgress is the current value of the animation transition that varies between 0 and 1. It’s needed for calculating constraints in the cell.

nextLayoutCellFrame is a property that returns the frame of the layout you switch to. It’s also used for the cell layout configuration during the process of transition.

layoutState is the current state of the layout.

TransitionLayout

The TransitionLayout class overrides two UICollectionViewLayout methods:

layoutAttributesForElementsInRect and layoutAttributesForItemAtIndexPath, where we set properties values for the class BaseLayoutAttributes.

TransitionManager

The TransitionManager class uses the UICollectionView’s startInteractiveTransitionToCollectionViewLayout method, where you point the layout it must switch to:

func startInteractiveTransition() {
       UIApplication.sharedApplication().beginIgnoringInteractionEvents()
       transitionLayout = collectionView.startInteractiveTransitionToCollectionViewLayout(destinationLayout, completion: { success, finish in
           if success && finish {
               self.collectionView.reloadData()
               UIApplication.sharedApplication().endIgnoringInteractionEvents()
           }
       }) as! TransitionLayout
       transitionLayout.layoutState = layoutState
       createUpdaterAndStart()
   }

The CADisplayLink class is used to control animation duration. This class helps calculate the animation progress depending on the animation duration preset:

private func createUpdaterAndStart() {
       start = CACurrentMediaTime()
       updater = CADisplayLink(target: self, selector: Selector("updateTransitionProgress"))
       updater.frameInterval = 1
       updater.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
   }

dynamic func updateTransitionProgress() {
       var progress = (updater.timestamp - start) / duration
       progress = min(1, progress)
       progress = max(0, progress)
       transitionLayout.transitionProgress = CGFloat(progress)

       transitionLayout.invalidateLayout()
       if progress == finishTransitionValue {
           collectionView.finishInteractiveTransition()
           updater.invalidate()
       }
   }

That’s it! Use our DisplaySwitcher in any way you like! Check it out on:

And here’s our Contact Display Switch animation on:

Tech

How We Created Animated My Day Watch App Inspired by Apple’s Activity App

Tech

Creating ForceBlur Animation for iOS Messaging Apps

Design

How We Created Tab Bar Animation for iOS

Design

UIDynamics, UIKit or OpenGL? 3 Types of iOS Animations for the Star Wars

Design

Implementing Search Filter Animation in Kotlin for Quora Meets LinkedIn, Our App Design Concept

Excited to create something outstanding?

We share the same interests.

Let's team up!