Skip to content

Latest commit

 

History

History
190 lines (139 loc) · 5.42 KB

animations.md

File metadata and controls

190 lines (139 loc) · 5.42 KB

Animations using PinLayout

Basic Swift animations

Before explaining how to use PinLayout to animate views, you can check these two nice tutorials to learn the basic of animations:

PinLayout is stateless

It is important to remember that PinLayout is stateless, i.e. PinLayout always start from the view's current position and size (frame) when layouting a view. You don't need to reset anything to animate a view using PinLayout.

This also means that you can modify only the property you want to animate, for example the following code will only animate the view's width:

   UIView.animate(withDuration: 0.3) { 
      view.pin.width(30)
   }

Layout strategies

Multiple strategies can be used to animate layout using PinLayout. The choice is a question of preferences and the kind of animations you want to achieve.

Some possible strategies will be shown below using a simple example. The example animates a view position from left to right when the user tap a button.

Note that in the following source code the view's size was set to 150 px (view.pin.size(150)) in the initialization.

Basic strategy: Using UIView.setNeedsLayout and UIView.layoutIfNeeded

In this strategy, to force a call to layoutSubviews(), we call UIView.setNeedsLayout and UIView.layoutIfNeeded from the animation block.

var isViewLeftDocked = true

override func layoutSubviews() {
   super.layoutSubviews()

   if isViewLeftDocked {
      view.pin.top().left()
   } else {
      view.pin.top().right()
   }
}

func didTapTogglePosition() {
   isViewLeftDocked = !isViewLeftDocked
      
   UIView.animate(withDuration: 0.3) {
      self.setNeedsLayout()
      self.layoutIfNeeded()
   }
}

Using a layout method

This strategy use a private method to layout the animated view (layoutAnimatedView()). The advantage of this solution is that it is not required to call UIView.setNeedsLayout and UIView.layoutIfNeeded to relayout the view.

var isViewLeftDocked = true

override func layoutSubviews() {
   super.layoutSubviews()
   
   layoutAnimatedView()
}

private func layoutAnimatedView() {
   if isViewLeftDocked {
      view.pin.top().left()
   } else {
      view.pin.top().right()
   }
}

func didTapTogglePosition() {
   isViewLeftDocked = !isViewLeftDocked
   
   UIView.animate(withDuration: 0.3) { 
      self.layoutAnimatedView()
   }
}

Using an animation state

This strategy is similar to the previous one, but use an enumeration to keep the animation state.

enum AnimationState {
   case leftDocked
   case rightDocked
}

var animationState = AnimationState.leftDocked

override func layoutSubviews() {
   super.layoutSubviews()
   
   layoutAnimatedView()
}

private func layoutAnimatedView() {
   switch animationState {
   case .leftDocked:
      view.pin.top().left()
   case .rightDocked:
      view.pin.top().right()
   }
}

func didTapTogglePosition() {
   switch animationState {
   case .leftDocked: animationState = .rightDocked
   case .rightDocked: animationState = .leftDocked
   }
   
   UIView.animate(withDuration: 0.3) {
      self.layoutAnimatedView()
   }
}

Other strategies

It's really up to you to think of animation's strategies that match your situation. With PinLayout you are always in control of everything, including animations.


Collision between animations and layoutSubViews()

In some particular situation it is possible that layoutSubViews() may be called during the animation is in progress, this can occur particularly on long animation. To handle this kind of situation it is possible to use a boolean indicating if an animation is in progress, and to block temporarely the layout of animated views in layoutSubViews().

Here is an example:

enum AnimationState {
   case leftDocked
   case rightDocked
}

var animationState = AnimationState.leftDocked
var isAnimating = false

override func layoutSubviews() {
   super.layoutSubviews()
   
   // If an animation of the view is in progress, we don't update animated views position.
   guard !isAnimating else { return }
   layoutAnimatedView()
}

private func layoutAnimatedView() {
   switch animationState {
   case .leftDocked:
      view.pin.top().left()
   case .rightDocked:
      view.pin.top().right()
   }
}

func didTapTogglePosition() {
   switch animationState {
   case .leftDocked: animationState = .rightDocked
   case .rightDocked: animationState = .leftDocked
   }
   
   UIView.animate(withDuration: 0.3, animations: {
      self.isAnimating = true
      self.layoutAnimatedView()
   }, completion: { (_) in
      self.isAnimating = false
   })
}

Animation Example

You can check the animation example available in the PinLayout's Example App:

Source code