Commit db214d75 by Daniel Dahan Committed by GitHub

Merge pull request #864 from OrkhanAlikhanov/checkbox-radiobutton

CheckBox and RadioButton
parents 6b0bd134 5afe7ec3
......@@ -245,6 +245,10 @@
96E3C39A1D3A1CC20086A024 /* ErrorTextField.swift in Headers */ = {isa = PBXBuildFile; fileRef = 961F18E71CD93E3E008927C5 /* ErrorTextField.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96E3C39C1D3A1CC20086A024 /* Offset.swift in Headers */ = {isa = PBXBuildFile; fileRef = 968C99461D377849000074FF /* Offset.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96F1A5531F24F17A001D8CAF /* TabsController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96E09DC71F2287E50000B121 /* TabsController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9D47D0451F6D5E6700DBD569 /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D47D0441F6D5E6700DBD569 /* RadioButton.swift */; };
9D47D0491F6D5E7F00DBD569 /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D47D0461F6D5E7F00DBD569 /* Helper.swift */; };
9D47D04A1F6D5E7F00DBD569 /* CheckBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D47D0471F6D5E7F00DBD569 /* CheckBox.swift */; };
9D47D04B1F6D5E7F00DBD569 /* Base.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D47D0481F6D5E7F00DBD569 /* Base.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
......@@ -376,6 +380,10 @@
96E09DC71F2287E50000B121 /* TabsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabsController.swift; sourceTree = "<group>"; };
96E3C3931D397AE90086A024 /* Material+UIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+UIView.swift"; sourceTree = "<group>"; };
96F1DC871D654FDF0025F925 /* Material+CALayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+CALayer.swift"; sourceTree = "<group>"; };
9D47D0441F6D5E6700DBD569 /* RadioButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = "<group>"; };
9D47D0461F6D5E7F00DBD569 /* Helper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = "<group>"; };
9D47D0471F6D5E7F00DBD569 /* CheckBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckBox.swift; sourceTree = "<group>"; };
9D47D0481F6D5E7F00DBD569 /* Base.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Base.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXGroup section */
......@@ -758,6 +766,10 @@
96BCB7601CB40DC500C806FE /* FlatButton.swift */,
96BCB7931CB40DC500C806FE /* RaisedButton.swift */,
9658F2161CD6FA4700B902C1 /* IconButton.swift */,
9D47D0441F6D5E6700DBD569 /* RadioButton.swift */,
9D47D0481F6D5E7F00DBD569 /* Base.swift */,
9D47D0461F6D5E7F00DBD569 /* Helper.swift */,
9D47D0471F6D5E7F00DBD569 /* CheckBox.swift */,
);
name = Button;
sourceTree = "<group>";
......@@ -1105,9 +1117,11 @@
9615279D1F393C0E00E8B2AC /* CascadePreprocessor.swift in Sources */,
96A183651E0C6DD400083C30 /* FABMenuController.swift in Sources */,
965E81101DD4D5C800D61E4B /* NavigationBar.swift in Sources */,
9D47D0451F6D5E6700DBD569 /* RadioButton.swift in Sources */,
965E81111DD4D5C800D61E4B /* NavigationController.swift in Sources */,
965E81121DD4D5C800D61E4B /* NavigationItem.swift in Sources */,
9615278D1F393C0E00E8B2AC /* Motion+UIViewController.swift in Sources */,
9D47D0491F6D5E7F00DBD569 /* Helper.swift in Sources */,
965E81131DD4D5C800D61E4B /* NavigationDrawerController.swift in Sources */,
9656895F1F002F16001C656D /* CardCollectionViewCell.swift in Sources */,
965E81161DD4D5C800D61E4B /* DisplayStyle.swift in Sources */,
......@@ -1194,12 +1208,14 @@
965E80FB1DD4D59500D61E4B /* SearchBar.swift in Sources */,
965E80E01DD4C50600D61E4B /* Shape.swift in Sources */,
965E80E11DD4C50600D61E4B /* Offset.swift in Sources */,
9D47D04B1F6D5E7F00DBD569 /* Base.swift in Sources */,
961527981F393C0E00E8B2AC /* MotionPlugin.swift in Sources */,
961527801F393C0E00E8B2AC /* MotionAnimator.swift in Sources */,
965E80E21DD4C50600D61E4B /* View.swift in Sources */,
961527911F393C0E00E8B2AC /* MotionAnimation.swift in Sources */,
9615279A1F393C0E00E8B2AC /* MotionTransition.swift in Sources */,
9615279F1F393C0E00E8B2AC /* IgnoreSubviewModifiersPreprocessor.swift in Sources */,
9D47D04A1F6D5E7F00DBD569 /* CheckBox.swift in Sources */,
96328B7A1E020A41009A4C90 /* CollectionViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
......
//
// Base.swift
// Material
//
// Created by Orkhan Alikhanov on 8/29/17.
// Copyright © 2017 BiAtoms. All rights reserved.
//
import UIKit
class Base: UIControl {
let titleLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
prepare()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
prepare()
}
let iconLayer = CALayer()
var selectedColor = UIColor(red: 0.25, green: 0.32, blue: 0.71, alpha: 1.0)
var normalColor = UIColor.gray
func prepare() {
layer.addSublayer(iconLayer)
self.addTarget(self, action: #selector(didTap), for: .touchUpInside)
beforeFirstAnimation()
firstAnimation()
beforeSecondAnimation()
secondAnimation()
}
func didTap() {
self.isSelected = !self.isSelected
self.animate()
}
var sideLength: CGFloat { return frame.height }
override var intrinsicContentSize: CGSize { return CGSize(width: 132, height: 132) }
func beforeFirstAnimation() {}
func firstAnimation() {}
func beforeSecondAnimation() {}
func secondAnimation() {}
var isAnimating = false
let totalDuration: TimeInterval = 0.5
let delayFactor: TimeInterval = 0.33
var duration: TimeInterval { return totalDuration / (1.0 + delayFactor + 1.0) }
func animate() {
beforeFirstAnimation()
CATransaction.animate(withDuration: duration,
timingFunction: .easeInOut,
animations: {
self.isAnimating = true
self.firstAnimation()
}) {
CATransaction.performWithoutAnimation {
self.beforeSecondAnimation()
}
CATransaction.animate(withDuration: self.duration,
delay: self.duration * self.delayFactor,
timingFunction: .easeInOut,
animations: {
self.secondAnimation()
}, completion: { self.isAnimating = false })
}
}
override func layoutSubviews() {
super.layoutSubviews()
let size = CGSize(width: sideLength, height: sideLength)
self.iconLayer.frame.size = size
}
}
//
// CheckBox.swift
// Material
//
// Created by Orkhan Alikhanov on 8/29/17.
// Copyright © 2017 BiAtoms. All rights reserved.
//
import UIKit
class CheckBox: Base {
let borderLayer = CALayer()
var borderLayerFullBorderWidth: CGFloat { return sideLength / 2 * 1.1 } //without multipling 1.1 a weird plus sign (+) appears sometimes.
var borderLayerCenterDotBorderWidth: CGFloat { return sideLength / 2 * 0.87 }
var borderLayerNormalBorderWidth: CGFloat { return sideLength * 0.1 }
var borderLayerCornerRadius: CGFloat { return sideLength * 0.1 }
var borderLayerScalePercentageToShrink: CGFloat = 0.9
var borderLayerScaleToShrink: CATransform3D {
return CATransform3DMakeScale(borderLayerScalePercentageToShrink, borderLayerScalePercentageToShrink, 1)
}
var checkMarkLeftLayer = CAShapeLayer()
var checkMarkRightLayer = CAShapeLayer()
var checkMarkLayer = CALayer()
var checkMarkStartPoint: CGPoint {
return CGPoint(x: sideLength * 14 / 36, y: sideLength * 25 / 36)
}
var checkMarkRightEndPoint: CGPoint {
return CGPoint(x: sideLength - (sideLength * 6 / 36), y: sideLength * 9 / 36)
}
var checkMarkLeftEndPoint: CGPoint {
return CGPoint(x: sideLength * 6 / 36, y: sideLength * 18 / 36)
}
var checkMarkPathRigth: UIBezierPath {
let path = UIBezierPath()
path.move(to: checkMarkStartPoint)
path.addLine(to: checkMarkRightEndPoint)
return path
}
var checkMarkPathLeft: UIBezierPath {
let path = UIBezierPath()
path.move(to: checkMarkStartPoint)
path.addLine(to: checkMarkLeftEndPoint)
return path
}
var lineWidth: CGFloat {
return sideLength * 0.1
}
var checkmarkColor: UIColor = .white
override func prepare() {
super.prepare()
iconLayer.addSublayer(borderLayer)
iconLayer.addSublayer(checkMarkLayer)
checkMarkLayer.addSublayer(checkMarkLeftLayer)
checkMarkLayer.addSublayer(checkMarkRightLayer)
checkMarkLeftLayer.lineCap = kCALineCapSquare
checkMarkRightLayer.lineCap = kCALineCapSquare
}
override func beforeFirstAnimation() {
borderLayer.borderColor = (isSelected ? selectedColor : normalColor).cgColor
if !isSelected {
borderLayer.backgroundColor = normalColor.cgColor
}
checkMarkLayer.transform = .identity
}
override func firstAnimation() {
borderLayer.transform = borderLayerScaleToShrink
checkMarkLayer.transform = borderLayerScaleToShrink
if isSelected {
borderLayer.animate(#keyPath(CALayer.borderWidth),
from: borderLayerNormalBorderWidth,
to: borderLayerFullBorderWidth,
timingFunction: .easeIn)
} else {
checkMarkLeftLayer.animate(#keyPath(CAShapeLayer.strokeEnd),
from: 1,
to: 0,
timingFunction: .easeIn)
checkMarkRightLayer.animate(#keyPath(CAShapeLayer.strokeEnd),
from: 1,
to: 0,
timingFunction: .easeIn)
checkMarkLayer.transform = CATransform3DMakeTranslation(sideLength / 2 - checkMarkStartPoint.x, -(checkMarkStartPoint.y - sideLength / 2), 0)
}
}
override func beforeSecondAnimation() {
borderLayer.backgroundColor = (isSelected ? selectedColor : .clear).cgColor
if isSelected {
borderLayer.borderWidth = borderLayerNormalBorderWidth
checkMarkLeftLayer.strokeEnd = 0.0001
checkMarkRightLayer.strokeEnd = 0.0001
checkMarkLayer.animate(#keyPath(CALayer.opacity),
from: 0,
to: 1,
timingFunction: .easeIn,
dur: totalDuration * 0.1)
checkMarkLeftLayer.strokeColor = checkmarkColor.cgColor
checkMarkRightLayer.strokeColor = checkmarkColor.cgColor
checkMarkLeftLayer.path = checkMarkPathLeft.cgPath
checkMarkRightLayer.path = checkMarkPathRigth.cgPath
checkMarkLeftLayer.lineWidth = lineWidth
checkMarkRightLayer.lineWidth = lineWidth
} else {
borderLayer.borderWidth = borderLayerCenterDotBorderWidth
}
}
override func secondAnimation() {
borderLayer.transform = .identity
checkMarkLayer.transform = .identity
if isSelected {
checkMarkLeftLayer.animate(#keyPath(CAShapeLayer.strokeEnd),
from: 0,
to: 1,
timingFunction: .easeIn)
checkMarkRightLayer.animate(#keyPath(CAShapeLayer.strokeEnd),
from: 0,
to: 1,
timingFunction: .easeIn)
} else {
borderLayer.animate(#keyPath(CALayer.borderWidth),
from: borderLayerCenterDotBorderWidth,
to: borderLayerNormalBorderWidth,
timingFunction: .easeIn)
}
}
override func layoutSubviews() {
super.layoutSubviews()
guard !isAnimating else { return }
borderLayer.frame.size = CGSize(width: sideLength, height: sideLength)
checkMarkLayer.frame.size = borderLayer.frame.size
checkMarkLeftLayer.frame.size = borderLayer.frame.size
checkMarkRightLayer.frame.size = borderLayer.frame.size
borderLayer.borderWidth = borderLayerNormalBorderWidth
borderLayer.cornerRadius = borderLayerCornerRadius
}
}
//
// Helper.swift
// Material
//
// Created by Orkhan Alikhanov on 8/29/17.
// Copyright © 2017 BiAtoms. All rights reserved.
//
import UIKit
internal extension CATransaction {
enum TimingFunction {
case `default`
case linear
case easeIn
case easeOut
case easeInOut
var name: String {
switch self {
case .default:
return kCAMediaTimingFunctionDefault
case .linear:
return kCAMediaTimingFunctionLinear
case .easeIn:
return kCAMediaTimingFunctionEaseIn
case .easeOut:
return kCAMediaTimingFunctionEaseOut
case .easeInOut:
return kCAMediaTimingFunctionEaseInEaseOut
}
}
var media: CAMediaTimingFunction {
return CAMediaTimingFunction(name: name)
}
}
static func performWithoutAnimation(_ actionsWithoutAnimation: @escaping () -> Void) {
perform {
setDisableActions(true)
actionsWithoutAnimation()
}
}
static func animate(withDuration duration: TimeInterval,
delay: TimeInterval,
timingFunction: TimingFunction, animations: @escaping () -> Void, completion: (()->Void)? = nil) {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
animate(withDuration: duration, timingFunction: timingFunction, animations: animations, completion: completion)
}
}
static func animate(withDuration duration: TimeInterval, timingFunction: TimingFunction, animations: @escaping () -> Void, completion: (()->Void)? = nil) {
perform(with: completion) {
setAnimationDuration(duration)
setAnimationTimingFunction(timingFunction.media)
animations()
}
}
static func perform(with completion: (()->Void)? = nil, _ code: @escaping () -> Void) {
begin()
setCompletionBlock(completion)
code()
commit()
}
}
internal extension CALayer {
func animate(_ keyPath: String, from: Any?, to: Any?, timingFunction: CATransaction.TimingFunction = .default, dur: TimeInterval = 0 ) {
self.setValue(to, forKeyPath: keyPath)
let animation = CABasicAnimation(keyPath: keyPath)
animation.timingFunction = timingFunction.media
animation.fromValue = from
animation.toValue = to
animation.fillMode = kCAFillModeForwards
animation.duration = dur
self.add(animation, forKey: nil)
}
}
internal extension CATransform3D {
static var identity: CATransform3D {
return CATransform3DIdentity
}
}
//
// RadioButton.swift
// Material
//
// Created by Orkhan Alikhanov on 8/27/17.
// Copyright © 2017 BiAtoms. All rights reserved.
//
import UIKit
class RadioButton: Base {
let centerDot = CALayer()
let outerCircle = CALayer()
override func prepare() {
super.prepare()
iconLayer.addSublayer(centerDot)
iconLayer.addSublayer(outerCircle)
}
let percentageOfOuterCircleSizeToShrinkTo: CGFloat = 0.9
let percentageOfOuterCircleWidthToStart: CGFloat = 1
var outerCircleScaleToShrink: CATransform3D {
let s = percentageOfOuterCircleSizeToShrinkTo
return CATransform3DMakeScale(s, s, 1)
}
var centerDotScaleForMeeting: CATransform3D {
let s = ((outerCircleDiameter - 2 * percentageOfOuterCircleWidthToStart * outerCircleBorderWidth) * percentageOfOuterCircleSizeToShrinkTo) / centerDotDiameter
return CATransform3DMakeScale(s, s, 1)
}
var outerCircleFullBorderWidth: CGFloat {
return (self.outerCircleDiameter / 2.0) * 1.1 //without multipling 1.1 a weird plus sign (+) appears sometimes.
}
var centerDotDiameter: CGFloat {
return outerCircleDiameter / 2.0
}
var outerCircleDiameter: CGFloat {
return sideLength// * (56.0 / 135.0)
}
var outerCircleBorderWidth: CGFloat {
return outerCircleDiameter * 0.11
}
override func beforeFirstAnimation() {
outerCircle.borderColor = (isSelected ? selectedColor : normalColor).cgColor
if !isSelected {
centerDot.backgroundColor = normalColor.cgColor
}
}
override func firstAnimation() {
outerCircle.transform = outerCircleScaleToShrink
let to = isSelected ? outerCircleDiameter / 2.0 : outerCircleBorderWidth * percentageOfOuterCircleWidthToStart
self.outerCircle.animate(#keyPath(CALayer.borderWidth),
from: outerCircleBorderWidth,
to: to,
timingFunction: .easeIn)
if !isSelected {
self.centerDot.transform = centerDotScaleForMeeting
}
}
override func beforeSecondAnimation() {
self.centerDot.transform = isSelected ? centerDotScaleForMeeting : .identity
self.centerDot.backgroundColor = (isSelected ? selectedColor : .clear).cgColor
if !isSelected {
self.outerCircle.borderWidth = outerCircleFullBorderWidth
}
}
override func secondAnimation() {
outerCircle.transform = .identity
let from = isSelected ? outerCircleBorderWidth * percentageOfOuterCircleWidthToStart : outerCircleFullBorderWidth
outerCircle.animate(#keyPath(CALayer.borderWidth),
from: from,
to: outerCircleBorderWidth,
timingFunction: .easeIn)
if isSelected {
centerDot.transform = .identity
}
}
override func layoutSubviews() {
super.layoutSubviews()
guard !isAnimating else { return }
centerDot.frame = CGRect(x: centerDotDiameter / 2.0, y: centerDotDiameter / 2.0, width: centerDotDiameter, height: centerDotDiameter)
outerCircle.frame.size = CGSize(width: outerCircleDiameter, height: outerCircleDiameter)
centerDot.cornerRadius = centerDot.bounds.width / 2
outerCircle.cornerRadius = outerCircleDiameter / 2
outerCircle.borderWidth = outerCircleBorderWidth
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment