Skip to content

Instantly share code, notes, and snippets.

@HarshilShah
Created September 14, 2017 07:33
Show Gist options
  • Save HarshilShah/a2faf44f5c9b5a0cf0e904f3e27c6cd6 to your computer and use it in GitHub Desktop.
Save HarshilShah/a2faf44f5c9b5a0cf0e904f3e27c6cd6 to your computer and use it in GitHub Desktop.
Control Center-like grabber view
//
// GrabberView.swift
//
// Created by Harshil Shah on 23/07/17.
// Copyright © 2017 Harshil Shah. All rights reserved.
//
import UIKit
final class GrabberView: UIView {
// MARK:- Private types
enum State {
case arrowUp
case flat
case arrowDown
}
// MARK:- Public variables
var state: GrabberView.State = .flat {
didSet { if oldValue != state { setMaskPath(animated: true) } }
}
// MARK:- Private variables
private var requiresUpdate = false
private let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
private let maskLayer = CALayer()
private let leftLayer = CAShapeLayer()
private let rightLayer = CAShapeLayer()
// MARK:- Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func awakeFromNib() {
super.awakeFromNib()
setup()
}
private func setup() {
leftLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 3, width: 18, height: 4), byRoundingCorners: [.topLeft, .bottomLeft], cornerRadii: CGSize(width: 2, height: 2)).cgPath
maskLayer.addSublayer(leftLayer)
rightLayer.path = UIBezierPath(roundedRect: CGRect(x: 18, y: 3, width: 18, height: 4), byRoundingCorners: [.topRight, .bottomRight], cornerRadii: CGSize(width: 2, height: 2)).cgPath
maskLayer.addSublayer(rightLayer)
blurView.layer.mask = maskLayer
blurView.translatesAutoresizingMaskIntoConstraints = false
addSubview(blurView)
NSLayoutConstraint.activate([
blurView.topAnchor.constraint(equalTo: topAnchor),
blurView.bottomAnchor.constraint(equalTo: bottomAnchor),
blurView.leadingAnchor.constraint(equalTo: leadingAnchor),
blurView.trailingAnchor.constraint(equalTo: trailingAnchor)
])
setMaskPath(animated: false)
}
// MARK:- UIView methods
override func layoutSubviews() {
super.layoutSubviews()
maskLayer.frame = bounds
leftLayer.frame = maskLayer.bounds
rightLayer.frame = maskLayer.bounds
if requiresUpdate {
setMaskPath(animated: false)
}
}
override var intrinsicContentSize : CGSize {
return CGSize(width: 36, height: 10)
}
// MARK:- Private methods
private func setMaskPath(forState targetState: GrabberView.State? = nil, animated: Bool) {
guard maskLayer.frame != .zero else {
requiresUpdate = true
return
}
requiresUpdate = false
let state = targetState ?? self.state
let rotationInRad: CGFloat = 0.35
let duration: TimeInterval = animated ? 0.2 : 0
let translation: CGFloat = {
switch state {
case .arrowUp: return -5
case .flat: return 0
case .arrowDown: return 5
}
}()
let rotationLeft: CGFloat = {
switch state {
case .arrowUp: return -rotationInRad
case .flat: return 0
case .arrowDown: return rotationInRad
}
}()
let rotationRight: CGFloat = {
switch state {
case .arrowUp: return rotationInRad
case .flat: return 0
case .arrowDown: return -rotationInRad
}
}()
let anchorPoint: CGPoint = {
switch state {
case .arrowUp: return CGPoint(x: 0.5, y: 0.3)
case .flat: return CGPoint(x: 0.5, y: 0.5)
case .arrowDown: return CGPoint(x: 0.5, y: 0.7)
}
}()
leftLayer.anchorPoint = anchorPoint
rightLayer.anchorPoint = anchorPoint
animateLayer(leftLayer, translation: translation, rotation: rotationLeft, duration: duration)
animateLayer(rightLayer, translation: translation, rotation: rotationRight, duration: duration)
}
private func animateLayer(_ layer: CAShapeLayer, translation: CGFloat, rotation: CGFloat, duration: TimeInterval) {
let initialTransform = CATransform3DMakeTranslation(0, translation, 0)
let finalTransform = CATransform3DRotate(initialTransform, rotation, 0, 0, 1)
let originalTransform = layer.transform
layer.transform = finalTransform
let animation = CABasicAnimation(keyPath: "transform")
animation.fromValue = originalTransform
animation.duration = duration
layer.add(animation, forKey: "transform")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment