Skip to content

Commit

Permalink
Merge pull request #52 from Aelines/patch-1
Browse files Browse the repository at this point in the history
Changing description of DisplaySwitcher
  • Loading branch information
OlgaIOs authored Jul 15, 2021
2 parents 7c44f1a + f6e0af4 commit 6bb247c
Showing 1 changed file with 127 additions and 6 deletions.
133 changes: 127 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@
[![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)]()
[![Yalantis](https://raw.githubusercontent.com/Yalantis/PullToRefresh/develop/PullToRefreshDemo/Resources/badge_dark.png)](https://yalantis.com/?utm_source=github)

This component implements custom transition between two collection view layouts with custom animation duration.
We designed a UI that allows users to switch between list and grid views on the fly and choose the most convenient display type. 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.

We created design mockups for both list and grid views using Sketch. As soon as the mockups were ready, we 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:

![Preview](https://github.com/Yalantis/DisplaySwitcher/blob/master/Assets/animation.gif)

Check this <a href="https://dribbble.com/shots/2276068-Contact-Display-Switch">project on dribbble</a>.

Also, read how it was done in [our blog](https://yalantis.com/blog/contact-display-switch-animation/)

## Requirements
- iOS 10.0+
- Xcode 11
Expand All @@ -33,6 +31,16 @@ pod ‘DisplaySwitcher’, '~> 2.0’
github "Yalantis/DisplaySwitcher" "master"
```
## 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
Our DisplaySwitcher component isn't limited to friends and contacts lists. It can work with any other content too.

## Usage

At first, import DisplaySwitcher:
Expand Down Expand Up @@ -93,7 +101,120 @@ rotationButton.selected = layoutState == .list
rotationButton.animationDuration = animationDuration
```

Have fun! :)
## Under the hood
We use five classes to implement our DisplaySwitcher:

### 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:

```swift
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:

```swift
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:

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

### BaseLayoutAttributes

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

```swift
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’sstartInteractiveTransitionToCollectionViewLayout method, where you point the layout it must switch to:

```swift
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()
}
```

### CADisplayLink class

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

```swift
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 [Dribbble](https://dribbble.com/shots/2276068-Contact-Display-Switch).

#### Let us know!

Expand Down

0 comments on commit 6bb247c

Please sign in to comment.