211 lines
8.1 KiB
Swift
Executable File
211 lines
8.1 KiB
Swift
Executable File
//
|
|
// CircleMenuButton.swift
|
|
//
|
|
// Copyright (c) 18/01/16. Ramotion Inc. (http://ramotion.com)
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
import UIKit
|
|
|
|
internal class CircleMenuButton: UIButton {
|
|
|
|
// MARK: properties
|
|
|
|
weak var container: UIView?
|
|
|
|
// MARK: life cycle
|
|
|
|
init(size: CGSize, platform: UIView, distance: Float, angle: Float = 0) {
|
|
super.init(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: size))
|
|
|
|
backgroundColor = UIColor(red: 0.79, green: 0.24, blue: 0.27, alpha: 1)
|
|
layer.cornerRadius = size.height / 2.0
|
|
|
|
let aContainer = createContainer(CGSize(width: size.width, height: CGFloat(distance)), platform: platform)
|
|
|
|
// hack view for rotate
|
|
let view = UIView(frame: CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height))
|
|
view.backgroundColor = UIColor.clear
|
|
view.addSubview(self)
|
|
// ...
|
|
|
|
aContainer.addSubview(view)
|
|
container = aContainer
|
|
|
|
view.layer.transform = CATransform3DMakeRotation(-CGFloat(angle.degrees), 0, 0, 1)
|
|
|
|
rotatedZ(angle: angle, animated: false)
|
|
}
|
|
|
|
internal required init?(coder _: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
// MARK: configure
|
|
|
|
fileprivate func createContainer(_ size: CGSize, platform: UIView) -> UIView {
|
|
let container = customize(UIView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: size))) {
|
|
$0.backgroundColor = UIColor.clear
|
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
|
$0.layer.anchorPoint = CGPoint(x: 0.5, y: 1)
|
|
}
|
|
platform.addSubview(container)
|
|
|
|
// added constraints
|
|
let height = NSLayoutConstraint(item: container,
|
|
attribute: .height,
|
|
relatedBy: .equal,
|
|
toItem: nil,
|
|
attribute: .height,
|
|
multiplier: 1,
|
|
constant: size.height)
|
|
height.identifier = "height"
|
|
container.addConstraint(height)
|
|
|
|
container.addConstraint(NSLayoutConstraint(item: container,
|
|
attribute: .width,
|
|
relatedBy: .equal,
|
|
toItem: nil,
|
|
attribute: .width,
|
|
multiplier: 1,
|
|
constant: size.width))
|
|
|
|
platform.addConstraint(NSLayoutConstraint(item: platform,
|
|
attribute: .centerX,
|
|
relatedBy: .equal,
|
|
toItem: container,
|
|
attribute: .centerX,
|
|
multiplier: 1,
|
|
constant: 0))
|
|
|
|
platform.addConstraint(NSLayoutConstraint(item: platform,
|
|
attribute: .centerY,
|
|
relatedBy: .equal,
|
|
toItem: container,
|
|
attribute: .centerY,
|
|
multiplier: 1,
|
|
constant: 0))
|
|
|
|
return container
|
|
}
|
|
|
|
// MARK: methods
|
|
|
|
internal func rotatedZ(angle: Float, animated: Bool, duration: Double = 0, delay: Double = 0) {
|
|
guard let container = self.container else {
|
|
fatalError("contaner don't create")
|
|
}
|
|
|
|
let rotateTransform = CATransform3DMakeRotation(CGFloat(angle.degrees), 0, 0, 1)
|
|
if animated {
|
|
UIView.animate(
|
|
withDuration: duration,
|
|
delay: delay,
|
|
options: UIView.AnimationOptions(),
|
|
animations: { () -> Void in
|
|
container.layer.transform = rotateTransform
|
|
},
|
|
completion: nil)
|
|
} else {
|
|
container.layer.transform = rotateTransform
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Animations
|
|
|
|
internal extension CircleMenuButton {
|
|
|
|
func showAnimation(distance: Float, duration: Double, delay: Double = 0) {
|
|
guard let heightConstraint = (self.container?.constraints.filter { $0.identifier == "height" })?.first else {
|
|
fatalError()
|
|
}
|
|
|
|
transform = CGAffineTransform(scaleX: 0, y: 0)
|
|
container?.superview?.layoutIfNeeded()
|
|
|
|
alpha = 0
|
|
|
|
heightConstraint.constant = CGFloat(distance)
|
|
UIView.animate(
|
|
withDuration: duration,
|
|
delay: delay,
|
|
usingSpringWithDamping: 0.7,
|
|
initialSpringVelocity: 0,
|
|
options: UIView.AnimationOptions.curveLinear,
|
|
animations: { () -> Void in
|
|
self.container?.superview?.layoutIfNeeded()
|
|
self.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
|
|
self.alpha = 1
|
|
}, completion: { (_) -> Void in
|
|
})
|
|
}
|
|
|
|
func hideAnimation(distance: Float, duration: Double, delay: Double = 0) {
|
|
guard let heightConstraint = (self.container?.constraints.filter { $0.identifier == "height" })?.first else {
|
|
return
|
|
}
|
|
|
|
heightConstraint.constant = CGFloat(distance)
|
|
UIView.animate(
|
|
withDuration: duration,
|
|
delay: delay,
|
|
options: UIView.AnimationOptions.curveEaseIn,
|
|
animations: { () -> Void in
|
|
self.container?.superview?.layoutIfNeeded()
|
|
self.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
|
|
}, completion: { (_) -> Void in
|
|
self.alpha = 0
|
|
|
|
if let _ = self.container {
|
|
self.container?.removeFromSuperview() // remove container
|
|
}
|
|
})
|
|
}
|
|
|
|
func changeDistance(_ distance: CGFloat, animated _: Bool, duration: Double = 0, delay: Double = 0) {
|
|
|
|
guard let heightConstraint = (self.container?.constraints.filter { $0.identifier == "height" })?.first else {
|
|
fatalError()
|
|
}
|
|
|
|
heightConstraint.constant = distance
|
|
|
|
UIView.animate(
|
|
withDuration: duration,
|
|
delay: delay,
|
|
options: UIView.AnimationOptions.curveEaseIn,
|
|
animations: { () -> Void in
|
|
self.container?.superview?.layoutIfNeeded()
|
|
},
|
|
completion: nil)
|
|
}
|
|
|
|
// MARK: layer animation
|
|
|
|
func rotationAnimation(_ angle: Float, duration: Double) {
|
|
let rotation = customize(CABasicAnimation(keyPath: "transform.rotation")) {
|
|
$0.duration = TimeInterval(duration)
|
|
$0.toValue = (angle.degrees)
|
|
$0.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
|
}
|
|
container?.layer.add(rotation, forKey: "rotation")
|
|
}
|
|
}
|