Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 883ade3052 | |||
| 44837098ea | |||
| 42063749ed | |||
| 464df2eb5c | |||
| 6f48df5761 | |||
| e331793afc | |||
| 82152b698a | |||
| 75eaaec598 | |||
| d2cc2e424c | |||
| 0ac35b4494 | |||
| 6c2600dea8 | |||
| 26d7422216 | |||
| a4a02b598f | |||
| 0309bb2e0e | |||
| 4e0507f132 | |||
| 8e33ff7614 | |||
| b7ab9e0327 | |||
| 4129f23d00 | |||
| 91b01d83d9 | |||
| 4eda691bd0 | |||
| 0889352299 | |||
| 34f267d43c | |||
| 425f8ec1ca | |||
| 25ea7eab4c | |||
| 16f060820f | |||
| 4c8faf7695 | |||
| 5b843582d4 |
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
+12
-8
@@ -21,14 +21,18 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UITableViewController {
|
||||
public struct SPConstraints {
|
||||
|
||||
func refreshManually() {
|
||||
self.refreshControl?.beginRefreshing()
|
||||
self.tableView.setContentOffset(
|
||||
CGPoint.init(
|
||||
x: 0,
|
||||
y: self.tableView.contentOffset.y - (self.refreshControl?.frame.size.height ?? 0)
|
||||
), animated: true)
|
||||
static func setEqualSize(_ view: UIView, superVuew: UIView) {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
view.topAnchor.constraint(equalTo: superVuew.topAnchor),
|
||||
view.leftAnchor.constraint(equalTo: superVuew.leftAnchor),
|
||||
view.rightAnchor.constraint(equalTo: superVuew.rightAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: superVuew.bottomAnchor)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+8
-5
@@ -21,13 +21,16 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UIDevice {
|
||||
extension UIVisualEffectView {
|
||||
|
||||
public var isIphone: Bool {
|
||||
return UIDevice.current.userInterfaceIdiom == .phone
|
||||
convenience init(style: UIBlurEffect.Style) {
|
||||
let effect = UIBlurEffect(style: style)
|
||||
self.init(effect: effect)
|
||||
}
|
||||
|
||||
public var isIpad: Bool {
|
||||
return UIDevice.current.userInterfaceIdiom == .pad
|
||||
convenience init(vibrancy style: UIBlurEffect.Style) {
|
||||
let effect = UIBlurEffect(style: style)
|
||||
let vibrancyEffect = UIVibrancyEffect(blurEffect: effect)
|
||||
self.init(effect: vibrancyEffect)
|
||||
}
|
||||
}
|
||||
+39
-27
@@ -21,38 +21,50 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIBezierPath {
|
||||
class SPAppleMusicButton: SPButton {
|
||||
|
||||
func resizeTo(width: CGFloat) {
|
||||
let currentWidth = self.bounds.width
|
||||
let relativeFactor = width / currentWidth
|
||||
self.apply(CGAffineTransform(scaleX: relativeFactor, y: relativeFactor))
|
||||
var mode: Mode = .unselect {
|
||||
didSet {
|
||||
self.updateStyle(animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
func convertToImage(fill: Bool, stroke: Bool, color: UIColor = .black) -> UIImage {
|
||||
UIGraphicsBeginImageContextWithOptions(CGSize(width: self.bounds.width, height: self.bounds.height), false, 0.0)
|
||||
let context = UIGraphicsGetCurrentContext()
|
||||
context!.setStrokeColor(color.cgColor)
|
||||
context!.setFillColor(color.cgColor)
|
||||
if stroke {
|
||||
self.stroke()
|
||||
var selectColor: UIColor = UIColor.init(hex: "FD2D55") {
|
||||
didSet {
|
||||
self.updateStyle(animated: false)
|
||||
}
|
||||
if fill {
|
||||
self.fill()
|
||||
}
|
||||
|
||||
var baseColor: UIColor = UIColor.init(hex: "F8F7FC") {
|
||||
didSet {
|
||||
self.updateStyle(animated: false)
|
||||
}
|
||||
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return image!
|
||||
}
|
||||
|
||||
override func commonInit() {
|
||||
super.commonInit()
|
||||
self.layer.cornerRadius = 8
|
||||
self.titleLabel?.font = UIFont.system(type: .DemiBold, size: 15)
|
||||
self.contentEdgeInsets = UIEdgeInsets.init(top: 12, left: 27, bottom: 12, right: 27)
|
||||
self.mode = .unselect
|
||||
}
|
||||
|
||||
private func updateStyle(animated: Bool) {
|
||||
switch self.mode {
|
||||
case .select:
|
||||
self.backgroundColor = self.selectColor
|
||||
self.setTitleColor(UIColor.white)
|
||||
break
|
||||
case .unselect:
|
||||
self.backgroundColor = self.baseColor
|
||||
self.setTitleColor(self.selectColor)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
enum Mode {
|
||||
case select
|
||||
case unselect
|
||||
}
|
||||
}
|
||||
|
||||
public struct SPBezierPath {
|
||||
|
||||
public static func setContext() {
|
||||
UIGraphicsBeginImageContextWithOptions(CGSize(width: 1, height: 1), false, 0)
|
||||
}
|
||||
|
||||
public static func endContext() {
|
||||
UIGraphicsEndImageContext()
|
||||
}
|
||||
}
|
||||
+30
-23
@@ -21,35 +21,42 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIWindow {
|
||||
public class SPButton: UIButton {
|
||||
|
||||
static var key: UIWindow? {
|
||||
return UIApplication.shared.keyWindow
|
||||
var gradientView: SPGradientView? {
|
||||
didSet {
|
||||
self.gradientView?.isUserInteractionEnabled = false
|
||||
if self.gradientView?.superview == nil {
|
||||
if self.gradientView != nil {
|
||||
self.insertSubview(self.gradientView!, at: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static var topSafeArea: CGFloat {
|
||||
var topSafeArea: CGFloat = 0
|
||||
if let window = UIWindow.key {
|
||||
if #available(iOS 11.0, *) {
|
||||
topSafeArea = window.safeAreaInsets.top
|
||||
}
|
||||
} else {
|
||||
topSafeArea = 0
|
||||
var round: Bool = false {
|
||||
didSet {
|
||||
self.layoutSubviews()
|
||||
}
|
||||
|
||||
return topSafeArea
|
||||
}
|
||||
|
||||
static var bottomSafeArea: CGFloat {
|
||||
var bottomSafeArea: CGFloat = 0
|
||||
if let window = UIWindow.key {
|
||||
if #available(iOS 11.0, *) {
|
||||
bottomSafeArea = window.safeAreaInsets.bottom
|
||||
}
|
||||
} else {
|
||||
bottomSafeArea = 0
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
internal func commonInit() {}
|
||||
|
||||
override public func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.gradientView?.setEqualsBoundsFromSuperview()
|
||||
if self.round {
|
||||
self.round()
|
||||
}
|
||||
|
||||
return bottomSafeArea
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
class SPDotButton: SPButton {
|
||||
|
||||
var customSideSize: CGFloat = 26 {
|
||||
didSet {
|
||||
self.sizeToFit()
|
||||
}
|
||||
}
|
||||
|
||||
var dotColor: UIColor = UIColor.white {
|
||||
didSet {
|
||||
for dotView in self.dotsView {
|
||||
dotView.backgroundColor = self.dotColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override var isHighlighted: Bool{
|
||||
didSet{
|
||||
if isHighlighted{
|
||||
UIView.animate(withDuration: 0.1, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 1.0, options: [.curveEaseOut, .beginFromCurrentState], animations: {
|
||||
for dotView in self.dotsView {
|
||||
dotView.alpha = 0.35
|
||||
}
|
||||
}, completion: nil)
|
||||
}else{
|
||||
UIView.animate(withDuration: 0.35, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 1.0, options: [.curveEaseOut, .beginFromCurrentState], animations: {
|
||||
for dotView in self.dotsView {
|
||||
dotView.alpha = 1
|
||||
}
|
||||
}, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var dotsView: [UIView] = []
|
||||
|
||||
override func commonInit() {
|
||||
super.commonInit()
|
||||
self.backgroundColor = UIColor.black.withAlphaComponent(0.2)
|
||||
for _ in 0...2 {
|
||||
let dotView = UIView()
|
||||
dotView.isUserInteractionEnabled = false
|
||||
dotView.backgroundColor = self.dotColor
|
||||
self.dotsView.append(dotView)
|
||||
self.addSubview(dotView)
|
||||
}
|
||||
}
|
||||
|
||||
override func sizeToFit() {
|
||||
super.sizeToFit()
|
||||
self.setWidth(self.customSideSize)
|
||||
self.setHeight(self.customSideSize)
|
||||
self.layoutSubviews()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let space: CGFloat = 2
|
||||
let sideSize: CGFloat = 4
|
||||
|
||||
let insest: CGFloat = (self.frame.width - (sideSize * 3) - (space * 2)) / 2
|
||||
|
||||
var currentXPosition: CGFloat = insest
|
||||
|
||||
for dotView in self.dotsView {
|
||||
dotView.setWidth(sideSize)
|
||||
dotView.setHeight(sideSize)
|
||||
dotView.setYCenteringFromSuperview()
|
||||
dotView.frame.origin.x = currentXPosition
|
||||
dotView.round()
|
||||
currentXPosition += (sideSize + space)
|
||||
}
|
||||
|
||||
self.round()
|
||||
}
|
||||
}
|
||||
+23
-16
@@ -21,31 +21,31 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPNativeOS11Button: SPDownloadingButton {
|
||||
class SPNativeLargeButton: SPDownloadingButton {
|
||||
|
||||
override var isHighlighted: Bool {
|
||||
didSet {
|
||||
if isHighlighted {
|
||||
self.backgroundColor = self.backgroundColor?.withAlphaComponent(0.7)
|
||||
if self.gradientView == nil {
|
||||
if isHighlighted {
|
||||
self.backgroundColor = self.backgroundColor?.withAlphaComponent(0.7)
|
||||
} else {
|
||||
self.backgroundColor = self.backgroundColor?.withAlphaComponent(1)
|
||||
}
|
||||
} else {
|
||||
self.backgroundColor = self.backgroundColor?.withAlphaComponent(1)
|
||||
self.backgroundColor = self.backgroundColor?.withAlphaComponent(0)
|
||||
if isHighlighted {
|
||||
self.gradientView?.alpha = 0.7
|
||||
} else {
|
||||
self.gradientView?.alpha = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
override func commonInit() {
|
||||
super.commonInit()
|
||||
self.titleLabel?.font = UIFont.system(type: UIFont.BoldType.DemiBold, size: 16)
|
||||
self.setTitleColorForNoramlAndHightlightedStates(color: UIColor.white)
|
||||
self.setTitleColor(UIColor.white)
|
||||
self.backgroundColor = SPNativeStyleKit.Colors.blue
|
||||
self.layer.masksToBounds = true
|
||||
self.layer.cornerRadius = 8
|
||||
@@ -61,5 +61,12 @@ class SPNativeOS11Button: SPDownloadingButton {
|
||||
self.setWidth(width)
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.gradientView?.setEqualsBoundsFromSuperview()
|
||||
self.gradientView?.layer.cornerRadius = self.layer.cornerRadius
|
||||
self.gradientView?.gradientLayer.cornerRadius = self.layer.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPSocialIconButton: UIButton {
|
||||
class SPSocialButton: UIButton {
|
||||
|
||||
let iconView = SPSocialIconView.init()
|
||||
var widthIconFactor: CGFloat = 0.5
|
||||
@@ -0,0 +1,202 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
public class SPFakeBarView: UIView {
|
||||
|
||||
var style: SPNavigationTitleStyle = . small {
|
||||
didSet {
|
||||
self.updateStyle()
|
||||
}
|
||||
}
|
||||
|
||||
private var settedHeight: CGFloat = 0
|
||||
|
||||
var height: CGFloat {
|
||||
get {
|
||||
return (self.settedHeight) + (self.addStatusBarHeight ? UIViewController.statusBarHeight : 0)
|
||||
}
|
||||
set {
|
||||
self.settedHeight = newValue
|
||||
self.updateHeight()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var addStatusBarHeight: Bool = true {
|
||||
didSet {
|
||||
self.updateHeight()
|
||||
}
|
||||
}
|
||||
|
||||
var elementsColor: UIColor = UINavigationController.elementsColor {
|
||||
didSet {
|
||||
self.leftButton.setTitleColor(self.elementsColor)
|
||||
self.rightButton.setTitleColor(self.elementsColor)
|
||||
}
|
||||
}
|
||||
|
||||
var titleLabel = UILabel.init()
|
||||
var subtitleLabel = UILabel.init()
|
||||
var leftButton = UIButton.init()
|
||||
var rightButton = UIButton.init()
|
||||
|
||||
private var titleBottomConstraint: NSLayoutConstraint?
|
||||
private var heightConstraint: NSLayoutConstraint?
|
||||
private var topConstraint: NSLayoutConstraint?
|
||||
private var leadingConstraint: NSLayoutConstraint?
|
||||
private var trailingConstraint: NSLayoutConstraint?
|
||||
private let blurView = UIVisualEffectView.init(style: .extraLight)
|
||||
private let separatorView = UIView()
|
||||
|
||||
init(style: SPNavigationTitleStyle) {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.style = style
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.style = .small
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
self.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
self.addSubview(self.blurView)
|
||||
self.blurView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.blurView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
|
||||
self.blurView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
|
||||
self.blurView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
|
||||
self.blurView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
|
||||
|
||||
self.addSubview(self.separatorView)
|
||||
self.separatorView.backgroundColor = UIColor.init(hex: "BFBFBF")
|
||||
self.separatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.separatorView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
|
||||
self.separatorView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
|
||||
self.separatorView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
|
||||
self.separatorView.heightAnchor.constraint(equalToConstant: 0.5).isActive = true
|
||||
|
||||
self.addSubview(self.titleLabel)
|
||||
self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16).isActive = true
|
||||
self.titleLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16).isActive = true
|
||||
self.titleBottomConstraint = self.titleLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -12)
|
||||
self.titleBottomConstraint?.isActive = true
|
||||
|
||||
self.addSubview(self.subtitleLabel)
|
||||
self.subtitleLabel.textColor = UIColor.init(hex: "8E8E92")
|
||||
self.subtitleLabel.font = UIFont.system(type: .DemiBold, size: 13)
|
||||
self.subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.subtitleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16).isActive = true
|
||||
self.subtitleLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16).isActive = true
|
||||
self.subtitleLabel.bottomAnchor.constraint(equalTo: self.titleLabel.topAnchor, constant: 0).isActive = true
|
||||
|
||||
self.leftButton.setTitleColor(self.elementsColor)
|
||||
self.leftButton.titleLabel?.textAlignment = .left
|
||||
self.leftButton.titleLabel?.font = UIFont.system(type: .DemiBold, size: 16)
|
||||
self.leftButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 17, bottom: 0, right: 0)
|
||||
self.addSubview(self.leftButton)
|
||||
self.leftButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.leftButton.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
|
||||
self.leftButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -12).isActive = true
|
||||
|
||||
self.rightButton.setTitleColor(self.elementsColor)
|
||||
self.rightButton.titleLabel?.textAlignment = .right
|
||||
self.rightButton.titleLabel?.font = UIFont.system(type: .DemiBold, size: 17)
|
||||
self.rightButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16)
|
||||
self.addSubview(self.rightButton)
|
||||
self.rightButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.rightButton.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
|
||||
self.rightButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -12).isActive = true
|
||||
|
||||
self.setContraints()
|
||||
self.updateStyle()
|
||||
}
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.setContraints()
|
||||
}
|
||||
|
||||
private func setContraints() {
|
||||
if let superview = self.superview {
|
||||
if self.topConstraint == nil {
|
||||
|
||||
self.topConstraint = self.topAnchor.constraint(equalTo: superview.topAnchor)
|
||||
self.topConstraint?.isActive = true
|
||||
self.leadingConstraint = self.leadingAnchor.constraint(equalTo: superview.leadingAnchor)
|
||||
self.leadingConstraint?.isActive = true
|
||||
self.trailingConstraint = self.trailingAnchor.constraint(equalTo: superview.trailingAnchor)
|
||||
self.trailingConstraint?.isActive = true
|
||||
|
||||
self.heightConstraint = self.heightAnchor.constraint(equalToConstant: self.height)
|
||||
self.heightConstraint?.isActive = true
|
||||
self.updateHeight()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateStyle() {
|
||||
switch self.style {
|
||||
case .small:
|
||||
if UIViewController.statusBarHeight == 44 {
|
||||
self.height = 88 - 44
|
||||
self.titleBottomConstraint?.constant = -12
|
||||
} else {
|
||||
self.height = 64 - 20
|
||||
self.titleBottomConstraint?.constant = -12
|
||||
}
|
||||
self.addStatusBarHeight = true
|
||||
self.titleLabel.font = UIFont.system(type: .DemiBold, size: 17)
|
||||
self.titleLabel.setCenteringAlignment()
|
||||
case .stork:
|
||||
self.height = 66
|
||||
self.titleBottomConstraint?.constant = -12
|
||||
self.addStatusBarHeight = false
|
||||
self.titleLabel.font = UIFont.system(type: .DemiBold, size: 17)
|
||||
self.titleLabel.setCenteringAlignment()
|
||||
case .large:
|
||||
if UIViewController.statusBarHeight == 44 {
|
||||
self.height = 140 - 44
|
||||
self.titleBottomConstraint?.constant = -8
|
||||
} else {
|
||||
self.height = 112 - 20
|
||||
self.titleBottomConstraint?.constant = -4
|
||||
}
|
||||
self.addStatusBarHeight = true
|
||||
self.titleLabel.font = UIFont.system(type: .Bold, size: 34)
|
||||
self.titleLabel.textAlignment = .left
|
||||
break
|
||||
}
|
||||
|
||||
self.updateConstraints()
|
||||
}
|
||||
|
||||
private func updateHeight() {
|
||||
self.heightConstraint?.constant = self.height
|
||||
self.updateConstraints()
|
||||
}
|
||||
}
|
||||
|
||||
+21
-13
@@ -21,28 +21,36 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPGradientButton: SPDownloadingButton {
|
||||
class SPImageView: UIImageView {
|
||||
|
||||
let gradientView = SPGradientView.init()
|
||||
var round: Bool = false {
|
||||
didSet {
|
||||
self.layoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.gradientView.setStartColorPosition(SPGradientView.Position.MediumLeft)
|
||||
self.gradientView.setEndColorPosition(SPGradientView.Position.MediumRight)
|
||||
self.gradientView.startColor = UIColor.init(hex: "5737F6")
|
||||
self.gradientView.endColor = UIColor.init(hex: "956BFE")
|
||||
self.gradientView.isUserInteractionEnabled = false
|
||||
self.layer.masksToBounds = true
|
||||
self.addSubview(self.gradientView)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
override init(image: UIImage?) {
|
||||
super.init(image: image)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
internal func commonInit() {}
|
||||
|
||||
override public func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.gradientView.setEqualsBoundsFromSuperview()
|
||||
if self.round {
|
||||
self.layer.masksToBounds = true
|
||||
self.round()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+9
-5
@@ -21,13 +21,17 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIScreen {
|
||||
public class SPLabel: UILabel {
|
||||
|
||||
var minSideSize: CGFloat {
|
||||
return min(UIScreen.main.bounds.width, UIScreen.main.bounds.height)
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
var widthLessThanHeight: Bool {
|
||||
return UIScreen.main.bounds.width < UIScreen.main.bounds.height
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
internal func commonInit() {}
|
||||
}
|
||||
+32
-24
@@ -21,35 +21,43 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPMengButton: SPGradientButton {
|
||||
class SPTextField: UITextField {
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
self.isFrameRounded = true
|
||||
|
||||
self.contentEdgeInsets = UIEdgeInsets.init(top: 12, left: 21, bottom: 12, right: 21)
|
||||
self.titleLabel?.font = UIFont.system(type: UIFont.BoldType.Bold, size: 17)
|
||||
self.setTitleColorForNoramlAndHightlightedStates(color: UIColor.white)
|
||||
self.layer.masksToBounds = true
|
||||
self.gradientView.layer.masksToBounds = true
|
||||
|
||||
self.gradientView.setStartColorPosition(SPGradientView.Position.MediumLeft)
|
||||
self.gradientView.setEndColorPosition(SPGradientView.Position.MediumRight)
|
||||
self.gradientView.startColor = UIColor.init(hex: "5737F6")
|
||||
self.gradientView.endColor = UIColor.init(hex: "956BFE")
|
||||
public var textInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) {
|
||||
didSet { setNeedsDisplay() }
|
||||
}
|
||||
|
||||
var cursorColor: UIColor = UIColor.blue {
|
||||
didSet {
|
||||
self.tintColor = self.cursorColor
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
if self.isFrameRounded {
|
||||
self.gradientView.round()
|
||||
self.round()
|
||||
}
|
||||
internal func commonInit() {}
|
||||
|
||||
open override func textRect(forBounds bounds: CGRect) -> CGRect {
|
||||
return bounds.inset(by: textInsets)
|
||||
}
|
||||
|
||||
open override func editingRect(forBounds bounds: CGRect) -> CGRect {
|
||||
return bounds.inset(by: textInsets)
|
||||
}
|
||||
|
||||
open override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
|
||||
return bounds.inset(by: textInsets)
|
||||
}
|
||||
|
||||
open override func drawText(in rect: CGRect) {
|
||||
super.drawText(in: rect.inset(by: textInsets))
|
||||
}
|
||||
}
|
||||
+25
-2
@@ -21,7 +21,30 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public struct SPStyleKit {
|
||||
public class SPView: UIView {
|
||||
|
||||
private init() {}
|
||||
var round: Bool = false {
|
||||
didSet {
|
||||
self.layoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
internal func commonInit() {}
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
if self.round {
|
||||
self.round()
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
public class SPCollectionContainerCell<ContentView: UIView>: UICollectionViewCell {
|
||||
|
||||
let view = ContentView.init()
|
||||
var currentIndexPath: IndexPath?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.addSubview(view)
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.view.setEqualsFrameFromBounds(self)
|
||||
}
|
||||
}
|
||||
+12
-21
@@ -25,29 +25,20 @@ public class SPCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
var currentIndexPath: IndexPath?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
internal func commonInit() {}
|
||||
|
||||
public override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
self.currentIndexPath = nil
|
||||
}
|
||||
}
|
||||
|
||||
public class SPCollectionContainerCell<ContentView: UIView>: UICollectionViewCell {
|
||||
|
||||
let view = ContentView.init()
|
||||
var currentIndexPath: IndexPath?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.addSubview(view)
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.view.setEqualsFrameFromBounds(self)
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -27,7 +27,7 @@ class SPImageCollectionViewCell: SPCollectionContainerCell<SPDownloadingImageVie
|
||||
super.init(frame: frame)
|
||||
self.view.layer.cornerRadius = 10
|
||||
self.view.contentMode = .scaleAspectFill
|
||||
self.view.setNativeStyle()
|
||||
self.view.setNative()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
@@ -37,6 +37,6 @@ class SPImageCollectionViewCell: SPCollectionContainerCell<SPDownloadingImageVie
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
self.view.contentMode = .scaleAspectFill
|
||||
self.view.setLoadingMode()
|
||||
self.view.startLoading()
|
||||
}
|
||||
}
|
||||
+1
-12
@@ -44,18 +44,7 @@ class SPMengTransformCollectionViewCell: SPCollectionViewCell {
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
|
||||
override func commonInit() {
|
||||
shadowContainerView.backgroundColor = UIColor.white
|
||||
shadowContainerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.addSubview(shadowContainerView)
|
||||
+1
-1
@@ -41,7 +41,7 @@ public class SPCollectionView: UICollectionView {
|
||||
commonInit()
|
||||
}
|
||||
|
||||
fileprivate func commonInit() {
|
||||
internal func commonInit() {
|
||||
self.layout.scrollDirection = .vertical
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.collectionViewLayout = self.layout
|
||||
+2
-12
@@ -46,17 +46,7 @@ class SPMengTransformCollectionView: SPCollectionView {
|
||||
var withParalax: Bool = true
|
||||
static var recomendedHeight: CGFloat = 310
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
override func commonInit() {
|
||||
self.layout.scrollDirection = .horizontal
|
||||
|
||||
self.layout.cellSideRatio = 1.23
|
||||
@@ -186,7 +176,7 @@ extension SPMengTransformCollectionView: UICollectionViewDelegate {
|
||||
let indexPath = self.indexPath(for: cell)!
|
||||
|
||||
let attributes = self.layoutAttributesForItem(at: indexPath)!
|
||||
if let rootController = SPRootViewController.controller {
|
||||
if let rootController = SPApp.rootController {
|
||||
let cellFrame = self.convert(attributes.frame, to: rootController.view)
|
||||
|
||||
if self.withParalax {
|
||||
+2
-2
@@ -291,8 +291,8 @@ class SPBaseContentTableViewCell: SPTableViewCell {
|
||||
|
||||
self.accessoryType = .none
|
||||
self.separatorInsetStyle = .beforeImage
|
||||
self.iconImageView.setLoadingMode()
|
||||
self.iconImageView.setNativeStyle()
|
||||
self.iconImageView.startLoading()
|
||||
self.iconImageView.setNative()
|
||||
|
||||
self.titleLabel.text = "Title"
|
||||
self.titleLabel.font = UIFont.system(type: UIFont.BoldType.Medium, size: 17)
|
||||
+1
-1
@@ -45,7 +45,7 @@ class SPFormButtonTableViewCell: UITableViewCell {
|
||||
self.backgroundColor = UIColor.white
|
||||
self.button.setTitle("Button", for: .normal)
|
||||
self.button.backgroundColor = UIColor.clear
|
||||
self.button.setTitleColorForNoramlAndHightlightedStates(color: SPNativeStyleKit.Colors.blue)
|
||||
self.button.setTitleColor(SPNativeStyleKit.Colors.blue)
|
||||
self.button.titleLabel?.font = UIFont.system(type: .Medium, size: 17)
|
||||
self.selectionStyle = .none
|
||||
self.contentView.addSubview(self.button)
|
||||
+1
-1
@@ -82,7 +82,7 @@ class SPFormFeaturedTitleTableViewCell: UITableViewCell {
|
||||
|
||||
self.withButton = false
|
||||
self.button.setTitle("Button Title", for: UIControl.State.normal)
|
||||
self.button.setTitleColorForNoramlAndHightlightedStates(color: SPNativeStyleKit.Colors.blue)
|
||||
self.button.setTitleColor(SPNativeStyleKit.Colors.blue)
|
||||
self.button.titleLabel?.font = UIFont.system(type: .Medium, size: 17)
|
||||
self.button.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.button.titleLabel?.textAlignment = .right
|
||||
@@ -0,0 +1,62 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
public struct SPStorkController {
|
||||
|
||||
static func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if let controller = self.controller(for: scrollView) {
|
||||
if let presentationController = controller.presentationController as? SPStorkPresentationController {
|
||||
let translation = -(scrollView.contentOffset.y + scrollView.contentInset.top)
|
||||
if translation >= 0 {
|
||||
scrollView.subviews.forEach {
|
||||
$0.transform = CGAffineTransform(translationX: 0, y: -translation)
|
||||
}
|
||||
if presentationController.pan?.state != UIGestureRecognizer.State.changed {
|
||||
presentationController.scrollViewDidScroll(translation)
|
||||
}
|
||||
} else {
|
||||
presentationController.scrollViewDidScroll(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private func controller(for view: UIView) -> UIViewController? {
|
||||
var nextResponder = view.next
|
||||
while nextResponder != nil && !(nextResponder! is UIViewController) {
|
||||
nextResponder = nextResponder!.next
|
||||
}
|
||||
return nextResponder as? UIViewController
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
|
||||
var isPresentedAsStork: Bool {
|
||||
return transitioningDelegate is SPStorkTransitioningDelegate
|
||||
&& modalPresentationStyle == .custom
|
||||
&& presentingViewController != nil
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -28,7 +28,7 @@ final class SPStorkDismissingAnimationController: NSObject, UIViewControllerAnim
|
||||
guard let presentedViewController = transitionContext.viewController(forKey: .from) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
|
||||
@@ -41,7 +41,7 @@ final class SPStorkDismissingAnimationController: NSObject, UIViewControllerAnim
|
||||
animations: {
|
||||
presentedViewController.view.frame = offscreenFrame
|
||||
}) { finished in
|
||||
transitionContext.completeTransition(finished)
|
||||
transitionContext.completeTransition(finished)
|
||||
}
|
||||
}
|
||||
|
||||
+16
-11
@@ -27,16 +27,16 @@ class SPStorkIndicatorView: UIView {
|
||||
didSet {
|
||||
switch self.style {
|
||||
case .line:
|
||||
SPAnimationSpring.animate(0.5, animations: {
|
||||
self.animate {
|
||||
self.leftView.transform = .identity
|
||||
self.rightView.transform = .identity
|
||||
}, options: .curveEaseOut)
|
||||
}
|
||||
case .arrow:
|
||||
SPAnimationSpring.animate(0.5, animations: {
|
||||
self.animate {
|
||||
let angle = CGFloat(20 * Float.pi / 180)
|
||||
self.leftView.transform = CGAffineTransform.init(rotationAngle: angle)
|
||||
self.rightView.transform = CGAffineTransform.init(rotationAngle: -angle)
|
||||
}, options: .curveEaseOut)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,32 +47,37 @@ class SPStorkIndicatorView: UIView {
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.addSubview(self.leftView)
|
||||
self.addSubview(self.rightView)
|
||||
self.leftView.backgroundColor = UIColor.init(hex: "CAC9CF")
|
||||
self.rightView.backgroundColor = UIColor.init(hex: "CAC9CF")
|
||||
self.leftView.backgroundColor = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1)
|
||||
self.rightView.backgroundColor = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
override func sizeToFit() {
|
||||
super.sizeToFit()
|
||||
self.setWidth(36)
|
||||
self.setHeight(13)
|
||||
self.frame = CGRect.init(x: self.frame.origin.x, y: self.frame.origin.y, width: 36, height: 13)
|
||||
|
||||
let height: CGFloat = 5
|
||||
let correction = height / 2
|
||||
|
||||
self.leftView.frame = CGRect.init(x: 0, y: 0, width: self.frame.width / 2 + correction, height: height)
|
||||
self.leftView.center.y = self.frame.height / 2
|
||||
self.leftView.round()
|
||||
self.leftView.layer.cornerRadius = min(self.leftView.frame.width, self.leftView.frame.height) / 2
|
||||
|
||||
self.rightView.frame = CGRect.init(x: self.frame.width / 2 - correction, y: 0, width: self.frame.width / 2 + correction, height: height)
|
||||
self.rightView.center.y = self.frame.height / 2
|
||||
self.rightView.round()
|
||||
self.rightView.layer.cornerRadius = min(self.leftView.frame.width, self.leftView.frame.height) / 2
|
||||
}
|
||||
|
||||
private func animate(animations: @escaping (() -> Void)) {
|
||||
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.beginFromCurrentState, .curveEaseOut], animations: {
|
||||
animations()
|
||||
})
|
||||
}
|
||||
|
||||
enum Style {
|
||||
+397
@@ -0,0 +1,397 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
class SPStorkPresentationController: UIPresentationController, UIGestureRecognizerDelegate {
|
||||
|
||||
var isSwipeToDismissEnabled: Bool = true
|
||||
var showIndicator: Bool = true
|
||||
var transitioningDelegate: SPStorkTransitioningDelegate?
|
||||
var pan: UIPanGestureRecognizer?
|
||||
|
||||
private var indicatorView = SPStorkIndicatorView()
|
||||
private var gradeView: UIView = UIView()
|
||||
private let snapshotViewContainer = UIView()
|
||||
private var snapshotView: UIView?
|
||||
private let backgroundView = UIView()
|
||||
|
||||
private var snapshotViewTopConstraint: NSLayoutConstraint?
|
||||
private var snapshotViewWidthConstraint: NSLayoutConstraint?
|
||||
private var snapshotViewAspectRatioConstraint: NSLayoutConstraint?
|
||||
private var workGester: Bool = false
|
||||
private var startDismissing: Bool = false
|
||||
|
||||
private var topSpace: CGFloat {
|
||||
let statusBarHeight: CGFloat = UIApplication.shared.statusBarFrame.height
|
||||
return (statusBarHeight < 25) ? 30 : statusBarHeight
|
||||
}
|
||||
|
||||
private var alpha: CGFloat {
|
||||
return 0.51
|
||||
}
|
||||
|
||||
private var cornerRadius: CGFloat {
|
||||
return 10
|
||||
}
|
||||
|
||||
private var scaleForPresentingView: CGFloat {
|
||||
guard let containerView = containerView else { return 0 }
|
||||
let factor = 1 - (topSpace * 2 / containerView.frame.height)
|
||||
return factor
|
||||
}
|
||||
|
||||
override var frameOfPresentedViewInContainerView: CGRect {
|
||||
guard let containerView = containerView else { return .zero }
|
||||
let yOffset: CGFloat = topSpace + 13
|
||||
return CGRect(x: 0, y: yOffset, width: containerView.bounds.width, height: containerView.bounds.height - yOffset)
|
||||
}
|
||||
|
||||
override func presentationTransitionWillBegin() {
|
||||
super.presentationTransitionWillBegin()
|
||||
|
||||
guard let containerView = self.containerView, let presentedView = self.presentedView, let window = containerView.window else { return }
|
||||
|
||||
if self.showIndicator {
|
||||
presentedView.addSubview(self.indicatorView)
|
||||
}
|
||||
self.updateLayoutIndicator()
|
||||
self.indicatorView.style = .arrow
|
||||
self.gradeView.alpha = 0
|
||||
|
||||
let initialFrame: CGRect = presentingViewController.isPresentedAsStork ? presentingViewController.view.frame : containerView.bounds
|
||||
|
||||
containerView.insertSubview(self.snapshotViewContainer, belowSubview: presentedViewController.view)
|
||||
self.snapshotViewContainer.frame = initialFrame
|
||||
self.updateSnapshot()
|
||||
self.backgroundView.backgroundColor = UIColor.black
|
||||
self.backgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||
containerView.insertSubview(self.backgroundView, belowSubview: self.snapshotViewContainer)
|
||||
NSLayoutConstraint.activate([
|
||||
self.backgroundView.topAnchor.constraint(equalTo: window.topAnchor),
|
||||
self.backgroundView.leftAnchor.constraint(equalTo: window.leftAnchor),
|
||||
self.backgroundView.rightAnchor.constraint(equalTo: window.rightAnchor),
|
||||
self.backgroundView.bottomAnchor.constraint(equalTo: window.bottomAnchor)
|
||||
])
|
||||
|
||||
let transformForSnapshotView = CGAffineTransform.identity
|
||||
.translatedBy(x: 0, y: -snapshotViewContainer.frame.origin.y)
|
||||
.translatedBy(x: 0, y: self.topSpace)
|
||||
.translatedBy(x: 0, y: -snapshotViewContainer.frame.height / 2)
|
||||
.scaledBy(x: scaleForPresentingView, y: scaleForPresentingView)
|
||||
.translatedBy(x: 0, y: snapshotViewContainer.frame.height / 2)
|
||||
|
||||
self.addCornerRadiusAnimation(for: self.snapshotView, cornerRadius: self.cornerRadius, duration: 0.6)
|
||||
self.snapshotView?.layer.masksToBounds = true
|
||||
if #available(iOS 11.0, *) {
|
||||
presentedView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
}
|
||||
presentedView.layer.cornerRadius = self.cornerRadius
|
||||
presentedView.layer.masksToBounds = true
|
||||
|
||||
var rootSnapshotView: UIView?
|
||||
var rootSnapshotRoundedView: UIView?
|
||||
|
||||
if presentingViewController.isPresentedAsStork {
|
||||
guard let rootController = presentingViewController.presentingViewController, let snapshotView = rootController.view.snapshotView(afterScreenUpdates: false) else { return }
|
||||
|
||||
containerView.insertSubview(snapshotView, aboveSubview: self.backgroundView)
|
||||
snapshotView.frame = initialFrame
|
||||
snapshotView.transform = transformForSnapshotView
|
||||
snapshotView.alpha = self.alpha
|
||||
snapshotView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotView.layer.masksToBounds = true
|
||||
rootSnapshotView = snapshotView
|
||||
|
||||
let snapshotRoundedView = UIView()
|
||||
snapshotRoundedView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotRoundedView.layer.masksToBounds = true
|
||||
containerView.insertSubview(snapshotRoundedView, aboveSubview: snapshotView)
|
||||
snapshotRoundedView.frame = initialFrame
|
||||
snapshotRoundedView.transform = transformForSnapshotView
|
||||
rootSnapshotRoundedView = snapshotRoundedView
|
||||
}
|
||||
|
||||
presentedViewController.transitionCoordinator?.animate(
|
||||
alongsideTransition: { [weak self] context in
|
||||
guard let `self` = self else { return }
|
||||
self.snapshotView?.transform = transformForSnapshotView
|
||||
self.gradeView.alpha = self.alpha
|
||||
}, completion: { _ in
|
||||
self.snapshotView?.transform = .identity
|
||||
rootSnapshotView?.removeFromSuperview()
|
||||
rootSnapshotRoundedView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
override func presentationTransitionDidEnd(_ completed: Bool) {
|
||||
super.presentationTransitionDidEnd(completed)
|
||||
guard let containerView = containerView else { return }
|
||||
self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView
|
||||
self.snapshotViewContainer.transform = .identity
|
||||
self.snapshotViewContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.snapshotViewContainer.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
|
||||
self.updateSnapshotAspectRatio()
|
||||
|
||||
if self.isSwipeToDismissEnabled {
|
||||
self.pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
|
||||
self.pan!.delegate = self
|
||||
self.pan!.maximumNumberOfTouches = 1
|
||||
self.pan!.cancelsTouchesInView = false
|
||||
self.presentedViewController.view.addGestureRecognizer(self.pan!)
|
||||
}
|
||||
}
|
||||
|
||||
override func dismissalTransitionWillBegin() {
|
||||
super.dismissalTransitionWillBegin()
|
||||
guard let containerView = containerView else { return }
|
||||
self.startDismissing = true
|
||||
|
||||
let initialFrame: CGRect = presentingViewController.isPresentedAsStork ? presentingViewController.view.frame : containerView.bounds
|
||||
|
||||
let initialTransform = CGAffineTransform.identity
|
||||
.translatedBy(x: 0, y: -initialFrame.origin.y)
|
||||
.translatedBy(x: 0, y: self.topSpace)
|
||||
.translatedBy(x: 0, y: -initialFrame.height / 2)
|
||||
.scaledBy(x: scaleForPresentingView, y: scaleForPresentingView)
|
||||
.translatedBy(x: 0, y: initialFrame.height / 2)
|
||||
|
||||
self.snapshotViewTopConstraint?.isActive = false
|
||||
self.snapshotViewWidthConstraint?.isActive = false
|
||||
self.snapshotViewAspectRatioConstraint?.isActive = false
|
||||
self.snapshotViewContainer.translatesAutoresizingMaskIntoConstraints = true
|
||||
self.snapshotViewContainer.frame = initialFrame
|
||||
self.snapshotViewContainer.transform = initialTransform
|
||||
|
||||
let finalCornerRadius = presentingViewController.isPresentedAsStork ? self.cornerRadius : 0
|
||||
let finalTransform: CGAffineTransform = .identity
|
||||
|
||||
self.addCornerRadiusAnimation(for: self.snapshotView, cornerRadius: finalCornerRadius, duration: 0.6)
|
||||
|
||||
var rootSnapshotView: UIView?
|
||||
var rootSnapshotRoundedView: UIView?
|
||||
|
||||
if presentingViewController.isPresentedAsStork {
|
||||
guard let rootController = presentingViewController.presentingViewController, let snapshotView = rootController.view.snapshotView(afterScreenUpdates: false) else { return }
|
||||
|
||||
containerView.insertSubview(snapshotView, aboveSubview: backgroundView)
|
||||
snapshotView.frame = initialFrame
|
||||
snapshotView.transform = initialTransform
|
||||
rootSnapshotView = snapshotView
|
||||
snapshotView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotView.layer.masksToBounds = true
|
||||
|
||||
let snapshotRoundedView = UIView()
|
||||
snapshotRoundedView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotRoundedView.layer.masksToBounds = true
|
||||
snapshotRoundedView.backgroundColor = UIColor.black.withAlphaComponent(1 - self.alpha)
|
||||
containerView.insertSubview(snapshotRoundedView, aboveSubview: snapshotView)
|
||||
snapshotRoundedView.frame = initialFrame
|
||||
snapshotRoundedView.transform = initialTransform
|
||||
rootSnapshotRoundedView = snapshotRoundedView
|
||||
}
|
||||
|
||||
presentedViewController.transitionCoordinator?.animate(
|
||||
alongsideTransition: { [weak self] context in
|
||||
guard let `self` = self else { return }
|
||||
self.snapshotView?.transform = .identity
|
||||
self.snapshotViewContainer.transform = finalTransform
|
||||
self.gradeView.alpha = 0
|
||||
}, completion: { _ in
|
||||
rootSnapshotView?.removeFromSuperview()
|
||||
rootSnapshotRoundedView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
override func dismissalTransitionDidEnd(_ completed: Bool) {
|
||||
super.dismissalTransitionDidEnd(completed)
|
||||
guard let containerView = containerView else { return }
|
||||
|
||||
self.backgroundView.removeFromSuperview()
|
||||
self.snapshotView?.removeFromSuperview()
|
||||
self.snapshotViewContainer.removeFromSuperview()
|
||||
|
||||
let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
presentedViewController.view.frame = offscreenFrame
|
||||
presentedViewController.view.transform = .identity
|
||||
}
|
||||
}
|
||||
|
||||
extension SPStorkPresentationController {
|
||||
|
||||
@objc func handlePan(gestureRecognizer: UIPanGestureRecognizer) {
|
||||
guard gestureRecognizer.isEqual(pan), self.isSwipeToDismissEnabled else { return }
|
||||
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
self.workGester = true
|
||||
self.indicatorView.style = .line
|
||||
self.presentingViewController.view.layer.removeAllAnimations()
|
||||
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: containerView)
|
||||
case .changed:
|
||||
self.workGester = true
|
||||
if self.isSwipeToDismissEnabled {
|
||||
let translation = gestureRecognizer.translation(in: presentedView)
|
||||
self.updatePresentedViewForTranslation(inVerticalDirection: translation.y)
|
||||
} else {
|
||||
gestureRecognizer.setTranslation(.zero, in: presentedView)
|
||||
}
|
||||
case .ended:
|
||||
self.workGester = false
|
||||
let translation = gestureRecognizer.translation(in: presentedView).y
|
||||
if translation >= 240 {
|
||||
presentedViewController.dismiss(animated: true, completion: nil)
|
||||
} else {
|
||||
self.indicatorView.style = .arrow
|
||||
UIView.animate(
|
||||
withDuration: 0.6,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 1,
|
||||
initialSpringVelocity: 1,
|
||||
options: [.curveEaseOut, .allowUserInteraction],
|
||||
animations: {
|
||||
self.snapshotView?.transform = .identity
|
||||
self.presentedView?.transform = .identity
|
||||
})
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ translation: CGFloat) {
|
||||
if !self.workGester {
|
||||
self.updatePresentedViewForTranslation(inVerticalDirection: translation)
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePresentedViewForTranslation(inVerticalDirection translation: CGFloat) {
|
||||
if self.startDismissing { return }
|
||||
|
||||
let elasticThreshold: CGFloat = 120
|
||||
let translationFactor: CGFloat = 1 / 2
|
||||
|
||||
if translation >= 0 {
|
||||
let translationForModal: CGFloat = {
|
||||
if translation >= elasticThreshold {
|
||||
let frictionLength = translation - elasticThreshold
|
||||
let frictionTranslation = 30 * atan(frictionLength / 120) + frictionLength / 10
|
||||
return frictionTranslation + (elasticThreshold * translationFactor)
|
||||
} else {
|
||||
return translation * translationFactor
|
||||
}
|
||||
}()
|
||||
|
||||
self.presentedView?.transform = CGAffineTransform(translationX: 0, y: translationForModal)
|
||||
|
||||
if !self.presentingViewController.isPresentedAsStork {
|
||||
let factor = 1 + (translationForModal / 6000)
|
||||
self.snapshotView?.transform = CGAffineTransform.init(scaleX: factor, y: factor)
|
||||
self.gradeView.alpha = self.alpha - ((factor - 1) * 15)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SPStorkPresentationController {
|
||||
|
||||
override func containerViewWillLayoutSubviews() {
|
||||
super.containerViewWillLayoutSubviews()
|
||||
guard let containerView = containerView else { return }
|
||||
self.updateSnapshotAspectRatio()
|
||||
if presentedViewController.view.isDescendant(of: containerView) {
|
||||
UIView.animate(withDuration: 0.1) { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
coordinator.animate(alongsideTransition: { contex in
|
||||
self.updateLayoutIndicator()
|
||||
}, completion: { [weak self] _ in
|
||||
self?.updateSnapshotAspectRatio()
|
||||
self?.updateSnapshot()
|
||||
})
|
||||
}
|
||||
|
||||
private func updateLayoutIndicator() {
|
||||
guard let presentedView = self.presentedView else { return }
|
||||
self.indicatorView.style = .line
|
||||
self.indicatorView.sizeToFit()
|
||||
self.indicatorView.frame.origin.y = 12
|
||||
self.indicatorView.center.x = presentedView.frame.width / 2
|
||||
}
|
||||
|
||||
private func updateSnapshot() {
|
||||
guard let currentSnapshotView = presentingViewController.view.snapshotView(afterScreenUpdates: true) else { return }
|
||||
self.snapshotView?.removeFromSuperview()
|
||||
self.snapshotViewContainer.addSubview(currentSnapshotView)
|
||||
self.constraints(view: currentSnapshotView, to: self.snapshotViewContainer)
|
||||
self.snapshotView = currentSnapshotView
|
||||
self.gradeView.removeFromSuperview()
|
||||
self.gradeView.backgroundColor = UIColor.black
|
||||
self.snapshotView!.addSubview(self.gradeView)
|
||||
self.constraints(view: self.gradeView, to: self.snapshotView!)
|
||||
}
|
||||
|
||||
private func updateSnapshotAspectRatio() {
|
||||
guard let containerView = containerView, snapshotViewContainer.translatesAutoresizingMaskIntoConstraints == false else { return }
|
||||
|
||||
self.snapshotViewTopConstraint?.isActive = false
|
||||
self.snapshotViewWidthConstraint?.isActive = false
|
||||
self.snapshotViewAspectRatioConstraint?.isActive = false
|
||||
|
||||
let snapshotReferenceSize = presentingViewController.view.frame.size
|
||||
let aspectRatio = snapshotReferenceSize.width / snapshotReferenceSize.height
|
||||
|
||||
self.snapshotViewTopConstraint = snapshotViewContainer.topAnchor.constraint(equalTo: containerView.topAnchor, constant: self.topSpace)
|
||||
self.snapshotViewWidthConstraint = snapshotViewContainer.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: scaleForPresentingView)
|
||||
self.snapshotViewAspectRatioConstraint = snapshotViewContainer.widthAnchor.constraint(equalTo: snapshotViewContainer.heightAnchor, multiplier: aspectRatio)
|
||||
|
||||
self.snapshotViewTopConstraint?.isActive = true
|
||||
self.snapshotViewWidthConstraint?.isActive = true
|
||||
self.snapshotViewAspectRatioConstraint?.isActive = true
|
||||
}
|
||||
|
||||
private func constraints(view: UIView, to superView: UIView) {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
view.topAnchor.constraint(equalTo: superView.topAnchor),
|
||||
view.leftAnchor.constraint(equalTo: superView.leftAnchor),
|
||||
view.rightAnchor.constraint(equalTo: superView.rightAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: superView.bottomAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
private func addCornerRadiusAnimation(for view: UIView?, cornerRadius: CGFloat, duration: CFTimeInterval) {
|
||||
guard let view = view else { return }
|
||||
let animation = CABasicAnimation(keyPath:"cornerRadius")
|
||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
|
||||
animation.fromValue = view.layer.cornerRadius
|
||||
animation.toValue = cornerRadius
|
||||
animation.duration = duration
|
||||
view.layer.add(animation, forKey: "cornerRadius")
|
||||
view.layer.cornerRadius = cornerRadius
|
||||
}
|
||||
}
|
||||
+2
@@ -24,9 +24,11 @@ import UIKit
|
||||
final class SPStorkPresentingAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
|
||||
guard let presentedViewController = transitionContext.viewController(forKey: .to) else {
|
||||
return
|
||||
}
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
containerView.addSubview(presentedViewController.view)
|
||||
presentedViewController.view.frame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
+3
@@ -23,6 +23,9 @@ import UIKit
|
||||
|
||||
public final class SPStorkTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
|
||||
|
||||
var isSwipeToDismissEnabled: Bool = true
|
||||
var showIndicator: Bool = true
|
||||
|
||||
public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
|
||||
let controller = SPStorkPresentationController(presentedViewController: presented, presenting: presenting)
|
||||
controller.transitioningDelegate = self
|
||||
@@ -31,5 +31,30 @@ struct SPApp {
|
||||
return Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
|
||||
}
|
||||
|
||||
static var rootController: UIViewController? {
|
||||
return UIApplication.shared.keyWindow?.rootViewController
|
||||
}
|
||||
|
||||
static func set(rootController: UIViewController, animatable: Bool = true) {
|
||||
|
||||
rootController.view.frame = UIScreen.main.bounds
|
||||
|
||||
let replaceRootViewController = {
|
||||
UIApplication.shared.keyWindow?.rootViewController = rootController
|
||||
}
|
||||
|
||||
if animatable {
|
||||
UIView.transition(
|
||||
with: UIApplication.shared.keyWindow ?? UIWindow(),
|
||||
duration: 0.5,
|
||||
options: UIView.AnimationOptions.transitionCrossDissolve,
|
||||
animations: {
|
||||
replaceRootViewController()
|
||||
}, completion: nil)
|
||||
} else {
|
||||
replaceRootViewController()
|
||||
}
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
public struct SPConstraintsAssistent {
|
||||
|
||||
static func setEqualSizeConstraint(_ subView: UIView, superVuew: UIView) {
|
||||
subView.translatesAutoresizingMaskIntoConstraints = false;
|
||||
let topMarginConstraint = NSLayoutConstraint(
|
||||
item: subView,
|
||||
attribute: NSLayoutConstraint.Attribute.topMargin,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: superVuew,
|
||||
attribute: NSLayoutConstraint.Attribute.top,
|
||||
multiplier: 1,
|
||||
constant: 0)
|
||||
|
||||
let bottomMarginConstraint = NSLayoutConstraint(
|
||||
item: subView,
|
||||
attribute: NSLayoutConstraint.Attribute.bottomMargin,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: superVuew,
|
||||
attribute: NSLayoutConstraint.Attribute.bottom,
|
||||
multiplier: 1,
|
||||
constant: 0)
|
||||
|
||||
let leadingMarginConstraint = NSLayoutConstraint(
|
||||
item: subView,
|
||||
attribute: NSLayoutConstraint.Attribute.leadingMargin,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: superVuew,
|
||||
attribute: NSLayoutConstraint.Attribute.leading,
|
||||
multiplier: 1,
|
||||
constant: 0)
|
||||
|
||||
let trailingMarginConstraint = NSLayoutConstraint(
|
||||
item: subView,
|
||||
attribute: NSLayoutConstraint.Attribute.trailingMargin,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: superVuew,
|
||||
attribute: NSLayoutConstraint.Attribute.trailing,
|
||||
multiplier: 1,
|
||||
constant: 0)
|
||||
|
||||
superVuew.addConstraints([
|
||||
topMarginConstraint, bottomMarginConstraint, leadingMarginConstraint, trailingMarginConstraint
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,11 +24,11 @@ import UIKit
|
||||
struct SPDevice {
|
||||
|
||||
static var isIphone: Bool {
|
||||
return UIDevice.current.isIphone
|
||||
return UIDevice.current.userInterfaceIdiom == .phone
|
||||
}
|
||||
|
||||
static var isIpad: Bool {
|
||||
return UIDevice.current.isIpad
|
||||
return UIDevice.current.userInterfaceIdiom == .pad
|
||||
}
|
||||
|
||||
struct Orientation {
|
||||
|
||||
@@ -23,10 +23,8 @@ import Foundation
|
||||
|
||||
extension Array {
|
||||
|
||||
func takeElements(count: Int) -> Array {
|
||||
if (count < self.count) {
|
||||
return Array(self[0..<count])
|
||||
}
|
||||
func get(count: Int) -> Array {
|
||||
if (count < self.count) { return Array(self[0..<count]) }
|
||||
return Array(self)
|
||||
}
|
||||
}
|
||||
@@ -35,11 +33,8 @@ extension Array where Element: Equatable {
|
||||
|
||||
mutating func removeDuplicates() {
|
||||
var result = [Element]()
|
||||
|
||||
for value in self {
|
||||
if result.contains(value) == false {
|
||||
result.append(value)
|
||||
}
|
||||
if result.contains(value) == false { result.append(value) }
|
||||
}
|
||||
self = result
|
||||
}
|
||||
@@ -48,9 +43,7 @@ extension Array where Element: Equatable {
|
||||
extension Array where Element: Hashable {
|
||||
|
||||
func after(item: Element) -> Element? {
|
||||
if let index = self.index(of: item), index + 1 < self.count {
|
||||
return self[index + 1]
|
||||
}
|
||||
if let index = self.index(of: item), index + 1 < self.count { return self[index + 1] }
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
func + (left: CGPoint, right: CGPoint) -> CGPoint {
|
||||
return CGPoint(x: left.x + right.x, y: left.y + right.y)
|
||||
}
|
||||
@@ -24,28 +24,16 @@ import UIKit
|
||||
extension CGRect {
|
||||
|
||||
var bottomXPosition: CGFloat {
|
||||
get {
|
||||
return self.origin.x + self.width
|
||||
}
|
||||
set {
|
||||
self.origin.x = newValue - self.width
|
||||
}
|
||||
get { return self.origin.x + self.width }
|
||||
set { self.origin.x = newValue - self.width }
|
||||
}
|
||||
|
||||
var bottomYPosition: CGFloat {
|
||||
get {
|
||||
return self.origin.y + self.height
|
||||
}
|
||||
set {
|
||||
self.origin.y = newValue - self.height
|
||||
}
|
||||
get { return self.origin.y + self.height }
|
||||
set { self.origin.y = newValue - self.height }
|
||||
}
|
||||
|
||||
var minSideSize: CGFloat {
|
||||
return min(self.width, self.height)
|
||||
}
|
||||
|
||||
var isWidthLessThanHeight: Bool {
|
||||
return self.width < self.height
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,15 +23,15 @@ import UIKit
|
||||
|
||||
extension CGSize {
|
||||
|
||||
func resize(newWidth: CGFloat) -> CGSize {
|
||||
func resize(width: CGFloat) -> CGSize {
|
||||
let relativeSideSize = self.width / self.height
|
||||
let newHeight = newWidth / relativeSideSize
|
||||
return CGSize.init(width: newWidth, height: newHeight)
|
||||
let newHeight = width / relativeSideSize
|
||||
return CGSize.init(width: width, height: newHeight)
|
||||
}
|
||||
|
||||
func resize(newHeight: CGFloat) -> CGSize {
|
||||
func resize(height: CGFloat) -> CGSize {
|
||||
let relativeSideSize = self.width / self.height
|
||||
let newWidth = newHeight * relativeSideSize
|
||||
return CGSize.init(width: newWidth, height: newHeight)
|
||||
let newWidth = height * relativeSideSize
|
||||
return CGSize.init(width: newWidth, height: height)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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 Foundation
|
||||
|
||||
extension Collection {
|
||||
|
||||
subscript (safe index: Index) -> Element? {
|
||||
return indices.contains(index) ? self[index] : nil
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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 Foundation
|
||||
|
||||
extension Date {
|
||||
|
||||
func formatted(as dateFormat: String) -> String {
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = dateFormat
|
||||
return dateFormatter.string(from: self)
|
||||
}
|
||||
}
|
||||
@@ -66,20 +66,6 @@ extension String {
|
||||
return false
|
||||
}
|
||||
|
||||
mutating func reduce(minimumFractionDigits: Int = 0, maximumFractionDigits: Int) {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.minimumFractionDigits = minimumFractionDigits
|
||||
formatter.maximumFractionDigits = maximumFractionDigits
|
||||
let int = Double(self)
|
||||
if int != nil {
|
||||
let number = NSNumber.init(value: int!)
|
||||
if var newValue = formatter.string(from: number) {
|
||||
newValue.replace(",", with: ".")
|
||||
self = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutating func replace(_ replacingString: String, with newString: String) {
|
||||
self = self.replacingOccurrences(of: replacingString, with: newString)
|
||||
}
|
||||
|
||||
@@ -65,7 +65,8 @@ extension UIButton {
|
||||
}
|
||||
|
||||
func setTitleColor(_ color: UIColor) {
|
||||
self.setTitleColorForNoramlAndHightlightedStates(color: color)
|
||||
self.setTitleColor(color, for: .normal)
|
||||
self.setTitleColor(color.withAlphaComponent(0.7), for: .highlighted)
|
||||
}
|
||||
|
||||
func removeAllTargets() {
|
||||
@@ -74,23 +75,18 @@ extension UIButton {
|
||||
|
||||
func showText(_ text: String, withComplection completion: (() -> Void)! = {}) {
|
||||
let baseText = self.titleLabel?.text ?? " "
|
||||
|
||||
SPAnimation.animate(0.2,
|
||||
animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
SPAnimation.animate(0.2, animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
}, withComplection: {
|
||||
self.setTitle(text, for: .normal)
|
||||
|
||||
SPAnimation.animate(0.2, animations: {
|
||||
self.titleLabel?.alpha = 1
|
||||
}, withComplection: {
|
||||
|
||||
SPAnimation.animate(0.2, animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
}, delay: 0.35,
|
||||
withComplection: {
|
||||
self.setTitle(baseText, for: .normal)
|
||||
|
||||
SPAnimation.animate(0.2, animations: {
|
||||
self.titleLabel?.alpha = 1
|
||||
}, withComplection: {
|
||||
@@ -102,13 +98,10 @@ extension UIButton {
|
||||
}
|
||||
|
||||
func setAnimatableText(_ text: String, withComplection completion: (() -> Void)! = {}) {
|
||||
|
||||
SPAnimation.animate(0.2,
|
||||
animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
SPAnimation.animate(0.2, animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
}, withComplection: {
|
||||
self.setTitle(text, for: .normal)
|
||||
|
||||
SPAnimation.animate(0.2, animations: {
|
||||
self.titleLabel?.alpha = 1
|
||||
}, withComplection: {
|
||||
@@ -118,25 +111,18 @@ extension UIButton {
|
||||
}
|
||||
|
||||
func hideContent(completion: (() -> Void)! = {}) {
|
||||
SPAnimation.animate(0.2,
|
||||
animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
SPAnimation.animate(0.2, animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
}, withComplection: {
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
func showContent(completion: (() -> Void)! = {}) {
|
||||
SPAnimation.animate(0.2,
|
||||
animations: {
|
||||
self.titleLabel?.alpha = 1
|
||||
SPAnimation.animate(0.2, animations: {
|
||||
self.titleLabel?.alpha = 1
|
||||
}, withComplection: {
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
func setTitleColorForNoramlAndHightlightedStates(color: UIColor) {
|
||||
self.setTitleColor(color, for: .normal)
|
||||
self.setTitleColor(color.withAlphaComponent(0.7), for: .highlighted)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public extension UIFont {
|
||||
return UIFont.init(
|
||||
name: self.getFontNameBy(fontType: fontType) + self.getBoldTypeNameBy(boldType: boldType),
|
||||
size: size
|
||||
)!
|
||||
)!
|
||||
}
|
||||
|
||||
private static func getFontNameBy(fontType: FontType) -> String {
|
||||
|
||||
@@ -23,334 +23,13 @@ import UIKit
|
||||
|
||||
public extension UIImage {
|
||||
|
||||
public func resize(newWidth: CGFloat) -> UIImage {
|
||||
let scale = newWidth / self.size.width
|
||||
public func resize(width: CGFloat) -> UIImage {
|
||||
let scale = width / self.size.width
|
||||
let newHeight = self.size.height * scale
|
||||
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
|
||||
self.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
|
||||
UIGraphicsBeginImageContext(CGSize(width: width, height: newHeight))
|
||||
self.draw(in: CGRect(x: 0, y: 0, width: width, height: newHeight))
|
||||
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return newImage!
|
||||
}
|
||||
|
||||
public func resize(to size: CGSize) -> UIImage {
|
||||
UIGraphicsBeginImageContextWithOptions(size, false, 0.0);
|
||||
self.draw(in: CGRect(origin: CGPoint.zero, size: CGSize(width: size.width, height: size.height)))
|
||||
let resizeImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
|
||||
UIGraphicsEndImageContext()
|
||||
return resizeImage
|
||||
}
|
||||
|
||||
public class func drawFromView(view: UIView) -> UIImage {
|
||||
UIGraphicsBeginImageContext(view.frame.size)
|
||||
view.layer.render(in: UIGraphicsGetCurrentContext()!)
|
||||
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return image!
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - get assest colors
|
||||
|
||||
extension UIImage {
|
||||
private func resizeForUIImageColors(newSize: CGSize) -> UIImage {
|
||||
UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
|
||||
defer {
|
||||
UIGraphicsEndImageContext()
|
||||
}
|
||||
self.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
|
||||
guard let result = UIGraphicsGetImageFromCurrentImageContext() else {
|
||||
fatalError("UIImageColors.resizeForUIImageColors failed: UIGraphicsGetImageFromCurrentImageContext returned nil.")
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func getColors(quality: UIImageAssestColorsQuality = .high, _ completion: @escaping (UIImageAssestColors) -> Void) {
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
let result = self.getColors(quality: quality)
|
||||
DispatchQueue.main.async {
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func getColors(quality: UIImageAssestColorsQuality = .high) -> UIImageAssestColors {
|
||||
var scaleDownSize: CGSize = self.size
|
||||
if quality != .highest {
|
||||
if self.size.width < self.size.height {
|
||||
let ratio = self.size.height/self.size.width
|
||||
scaleDownSize = CGSize(width: quality.rawValue/ratio, height: quality.rawValue)
|
||||
} else {
|
||||
let ratio = self.size.width/self.size.height
|
||||
scaleDownSize = CGSize(width: quality.rawValue, height: quality.rawValue/ratio)
|
||||
}
|
||||
}
|
||||
|
||||
let cgImage = self.resizeForUIImageColors(newSize: scaleDownSize).cgImage!
|
||||
let width: Int = cgImage.width
|
||||
let height: Int = cgImage.height
|
||||
|
||||
let threshold = Int(CGFloat(height)*0.01)
|
||||
var proposed: [Double] = [-1,-1,-1,-1]
|
||||
|
||||
guard let data = CFDataGetBytePtr(cgImage.dataProvider!.data) else {
|
||||
fatalError("UIImageColors.getColors failed: could not get cgImage data.")
|
||||
}
|
||||
|
||||
let imageColors = NSCountedSet(capacity: width*height)
|
||||
for x in 0..<width {
|
||||
for y in 0..<height {
|
||||
let pixel: Int = ((width * y) + x) * 4
|
||||
if 127 <= data[pixel+3] {
|
||||
imageColors.add((Double(data[pixel+2])*1000000)+(Double(data[pixel+1])*1000)+(Double(data[pixel])))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sortedColorComparator: Comparator = { (main, other) -> ComparisonResult in
|
||||
let m = main as! UIImageColorsCounter, o = other as! UIImageColorsCounter
|
||||
if m.count < o.count {
|
||||
return .orderedDescending
|
||||
} else if m.count == o.count {
|
||||
return .orderedSame
|
||||
} else {
|
||||
return .orderedAscending
|
||||
}
|
||||
}
|
||||
|
||||
var enumerator = imageColors.objectEnumerator()
|
||||
var sortedColors = NSMutableArray(capacity: imageColors.count)
|
||||
while let K = enumerator.nextObject() as? Double {
|
||||
let C = imageColors.count(for: K)
|
||||
if threshold < C {
|
||||
sortedColors.add(UIImageColorsCounter(color: K, count: C))
|
||||
}
|
||||
}
|
||||
sortedColors.sort(comparator: sortedColorComparator)
|
||||
|
||||
var proposedEdgeColor: UIImageColorsCounter
|
||||
if 0 < sortedColors.count {
|
||||
proposedEdgeColor = sortedColors.object(at: 0) as! UIImageColorsCounter
|
||||
} else {
|
||||
proposedEdgeColor = UIImageColorsCounter(color: 0, count: 1)
|
||||
}
|
||||
|
||||
if proposedEdgeColor.color.isBlackOrWhite && 0 < sortedColors.count {
|
||||
for i in 1..<sortedColors.count {
|
||||
let nextProposedEdgeColor = sortedColors.object(at: i) as! UIImageColorsCounter
|
||||
if Double(nextProposedEdgeColor.count)/Double(proposedEdgeColor.count) > 0.3 {
|
||||
if !nextProposedEdgeColor.color.isBlackOrWhite {
|
||||
proposedEdgeColor = nextProposedEdgeColor
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
proposed[0] = proposedEdgeColor.color
|
||||
|
||||
enumerator = imageColors.objectEnumerator()
|
||||
sortedColors.removeAllObjects()
|
||||
sortedColors = NSMutableArray(capacity: imageColors.count)
|
||||
let findDarkTextColor = !proposed[0].isDarkColor
|
||||
|
||||
while var K = enumerator.nextObject() as? Double {
|
||||
K = K.with(minSaturation: 0.15)
|
||||
if K.isDarkColor == findDarkTextColor {
|
||||
let C = imageColors.count(for: K)
|
||||
sortedColors.add(UIImageColorsCounter(color: K, count: C))
|
||||
}
|
||||
}
|
||||
sortedColors.sort(comparator: sortedColorComparator)
|
||||
|
||||
for color in sortedColors {
|
||||
let color = (color as! UIImageColorsCounter).color
|
||||
|
||||
if proposed[1] == -1 {
|
||||
if color.isContrasting(proposed[0]) {
|
||||
proposed[1] = color
|
||||
}
|
||||
} else if proposed[2] == -1 {
|
||||
if !color.isContrasting(proposed[0]) || !proposed[1].isDistinct(color) {
|
||||
continue
|
||||
}
|
||||
proposed[2] = color
|
||||
} else if proposed[3] == -1 {
|
||||
if !color.isContrasting(proposed[0]) || !proposed[2].isDistinct(color) || !proposed[1].isDistinct(color) {
|
||||
continue
|
||||
}
|
||||
proposed[3] = color
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let isDarkBackground = proposed[0].isDarkColor
|
||||
for i in 1...3 {
|
||||
if proposed[i] == -1 {
|
||||
proposed[i] = isDarkBackground ? 255255255:0
|
||||
}
|
||||
}
|
||||
|
||||
return UIImageAssestColors(
|
||||
background: proposed[0].uicolor,
|
||||
primary: proposed[1].uicolor,
|
||||
secondary: proposed[2].uicolor,
|
||||
detail: proposed[3].uicolor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public struct UIImageAssestColors {
|
||||
public var background: UIColor!
|
||||
public var primary: UIColor!
|
||||
public var secondary: UIColor!
|
||||
public var detail: UIColor!
|
||||
}
|
||||
|
||||
public enum UIImageAssestColorsQuality: CGFloat {
|
||||
case lowest = 50 // 50px
|
||||
case low = 100 // 100px
|
||||
case high = 250 // 250px
|
||||
case highest = 0 // No scale
|
||||
}
|
||||
|
||||
fileprivate struct UIImageColorsCounter {
|
||||
let color: Double
|
||||
let count: Int
|
||||
init(color: Double, count: Int) {
|
||||
self.color = color
|
||||
self.count = count
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension Double {
|
||||
|
||||
private var r: Double {
|
||||
return fmod(floor(self/1000000),1000000)
|
||||
}
|
||||
|
||||
private var g: Double {
|
||||
return fmod(floor(self/1000),1000)
|
||||
}
|
||||
|
||||
private var b: Double {
|
||||
return fmod(self,1000)
|
||||
}
|
||||
|
||||
fileprivate var isDarkColor: Bool {
|
||||
return (r*0.2126) + (g*0.7152) + (b*0.0722) < 127.5
|
||||
}
|
||||
|
||||
fileprivate var isBlackOrWhite: Bool {
|
||||
return (r > 232 && g > 232 && b > 232) || (r < 23 && g < 23 && b < 23)
|
||||
}
|
||||
|
||||
fileprivate func isDistinct(_ other: Double) -> Bool {
|
||||
let _r = self.r
|
||||
let _g = self.g
|
||||
let _b = self.b
|
||||
let o_r = other.r
|
||||
let o_g = other.g
|
||||
let o_b = other.b
|
||||
|
||||
return (fabs(_r-o_r) > 63.75 || fabs(_g-o_g) > 63.75 || fabs(_b-o_b) > 63.75)
|
||||
&& !(fabs(_r-_g) < 7.65 && fabs(_r-_b) < 7.65 && fabs(o_r-o_g) < 7.65 && fabs(o_r-o_b) < 7.65)
|
||||
}
|
||||
|
||||
fileprivate func with(minSaturation: Double) -> Double {
|
||||
// Ref: https://en.wikipedia.org/wiki/HSL_and_HSV
|
||||
|
||||
// Convert RGB to HSV
|
||||
|
||||
let _r = r/255
|
||||
let _g = g/255
|
||||
let _b = b/255
|
||||
var H, S, V: Double
|
||||
let M = fmax(_r,fmax(_g, _b))
|
||||
var C = M-fmin(_r,fmin(_g, _b))
|
||||
|
||||
V = M
|
||||
S = V == 0 ? 0:C/V
|
||||
|
||||
if minSaturation <= S {
|
||||
return self
|
||||
}
|
||||
|
||||
if C == 0 {
|
||||
H = 0
|
||||
} else if _r == M {
|
||||
H = fmod((_g-_b)/C, 6)
|
||||
} else if _g == M {
|
||||
H = 2+((_b-_r)/C)
|
||||
} else {
|
||||
H = 4+((_r-_g)/C)
|
||||
}
|
||||
|
||||
if H < 0 {
|
||||
H += 6
|
||||
}
|
||||
|
||||
// Back to RGB
|
||||
|
||||
C = V*minSaturation
|
||||
let X = C*(1-fabs(fmod(H,2)-1))
|
||||
var R, G, B: Double
|
||||
|
||||
switch H {
|
||||
case 0...1:
|
||||
R = C
|
||||
G = X
|
||||
B = 0
|
||||
case 1...2:
|
||||
R = X
|
||||
G = C
|
||||
B = 0
|
||||
case 2...3:
|
||||
R = 0
|
||||
G = C
|
||||
B = X
|
||||
case 3...4:
|
||||
R = 0
|
||||
G = X
|
||||
B = C
|
||||
case 4...5:
|
||||
R = X
|
||||
G = 0
|
||||
B = C
|
||||
case 5..<6:
|
||||
R = C
|
||||
G = 0
|
||||
B = X
|
||||
default:
|
||||
R = 0
|
||||
G = 0
|
||||
B = 0
|
||||
}
|
||||
|
||||
let m = V-C
|
||||
|
||||
return (floor((R + m)*255)*1000000)+(floor((G + m)*255)*1000)+floor((B + m)*255)
|
||||
}
|
||||
|
||||
fileprivate func isContrasting(_ color: Double) -> Bool {
|
||||
let bgLum = (0.2126*r)+(0.7152*g)+(0.0722*b)+12.75
|
||||
let fgLum = (0.2126*color.r)+(0.7152*color.g)+(0.0722*color.b)+12.75
|
||||
if bgLum > fgLum {
|
||||
return 1.6 < bgLum/fgLum
|
||||
} else {
|
||||
return 1.6 < fgLum/bgLum
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var uicolor: UIColor {
|
||||
return UIColor(red: CGFloat(r)/255, green: CGFloat(g)/255, blue: CGFloat(b)/255, alpha: 1)
|
||||
}
|
||||
|
||||
fileprivate var pretty: String {
|
||||
return "\(Int(self.r)), \(Int(self.g)), \(Int(self.b))"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,17 +23,12 @@ import UIKit
|
||||
|
||||
extension UIImageView {
|
||||
|
||||
public func setNativeStyle() {
|
||||
public func setNative() {
|
||||
self.layer.borderWidth = 0.5
|
||||
self.layer.borderColor = SPNativeStyleKit.Colors.midGray.cgColor
|
||||
self.layer.masksToBounds = true
|
||||
}
|
||||
|
||||
public func removeNativeStyle() {
|
||||
self.layer.borderWidth = 0
|
||||
self.layer.borderColor = UIColor.clear.cgColor
|
||||
}
|
||||
|
||||
public func downloadedFrom(url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit, withComplection complection: @escaping (UIImage?) -> () = {_ in }) {
|
||||
DispatchQueue.main.async {
|
||||
self.contentMode = mode
|
||||
|
||||
+5
-1
@@ -25,7 +25,11 @@ extension UINavigationController {
|
||||
|
||||
static var elementsColor: UIColor {
|
||||
get {
|
||||
return UINavigationBar.appearance().tintColor
|
||||
if UINavigationBar.appearance().tintColor != nil {
|
||||
return UINavigationBar.appearance().tintColor
|
||||
} else {
|
||||
return SPNativeStyleKit.Colors.blue
|
||||
}
|
||||
}
|
||||
set {
|
||||
UINavigationBar.appearance().tintColor = newValue
|
||||
|
||||
+6
-39
@@ -23,50 +23,17 @@ import UIKit
|
||||
|
||||
extension UITabBarController {
|
||||
|
||||
func addTabBarItem(titleName: String, imageName: String, viewController: UIViewController) {
|
||||
|
||||
let image = UIImage.init(named: imageName)
|
||||
|
||||
self.addTabBarItem(
|
||||
titleName: titleName,
|
||||
image: image ?? UIImage(),
|
||||
viewController: viewController
|
||||
)
|
||||
}
|
||||
|
||||
func addTabBarItem(titleName: String, image: UIImage, viewController: UIViewController) {
|
||||
func addTabBarItem(title: String, image: UIImage, selectedImage: UIImage? = nil, controller: UIViewController) {
|
||||
|
||||
let tabBarItem = UITabBarItem(
|
||||
title: titleName,
|
||||
title: title,
|
||||
image: image,
|
||||
selectedImage: image
|
||||
selectedImage: selectedImage ?? image
|
||||
)
|
||||
|
||||
viewController.tabBarItem = tabBarItem
|
||||
controller.tabBarItem = tabBarItem
|
||||
|
||||
if self.viewControllers == nil {
|
||||
self.viewControllers = [viewController]
|
||||
} else {
|
||||
self.viewControllers?.append(viewController)
|
||||
}
|
||||
if self.viewControllers == nil { self.viewControllers = [controller] }
|
||||
else { self.viewControllers?.append(controller) }
|
||||
}
|
||||
|
||||
@objc func addTabBarItem(titleName: String, image: UIImage, selectedImage: UIImage, viewController: UIViewController) {
|
||||
|
||||
let tabBarItem = UITabBarItem(
|
||||
title: titleName,
|
||||
image: image,
|
||||
selectedImage: selectedImage
|
||||
)
|
||||
|
||||
viewController.tabBarItem = tabBarItem
|
||||
|
||||
if self.viewControllers == nil {
|
||||
self.viewControllers = [viewController]
|
||||
} else {
|
||||
self.viewControllers?.append(viewController)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -36,50 +36,24 @@ extension UITableView {
|
||||
}
|
||||
|
||||
var lastSectionWithRows: Int? {
|
||||
|
||||
if self.numberOfSections == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if self.numberOfSections == 0 { return nil }
|
||||
var section = self.numberOfSections - 1
|
||||
|
||||
if section < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if section < 0 { return nil }
|
||||
while section >= 0 {
|
||||
|
||||
if self.numberOfRows(inSection: section) != 0 {
|
||||
return section
|
||||
}
|
||||
|
||||
if self.numberOfRows(inSection: section) != 0 { return section }
|
||||
section -= 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var firstSectionWithRows: Int? {
|
||||
|
||||
if self.numberOfSections == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if self.numberOfSections == 0 { return nil }
|
||||
var section = 0
|
||||
|
||||
if section > self.numberOfSections - 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if section > self.numberOfSections - 1 { return nil }
|
||||
while section <= (self.numberOfSections - 1) {
|
||||
|
||||
if self.numberOfRows(inSection: section) != 0 {
|
||||
return section
|
||||
}
|
||||
|
||||
if self.numberOfRows(inSection: section) != 0 { return section }
|
||||
section += 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
+13
-11
@@ -39,7 +39,6 @@ extension UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Keyboard
|
||||
extension UIViewController {
|
||||
|
||||
func dismissKeyboardWhenTappedAround() {
|
||||
@@ -53,7 +52,6 @@ extension UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Add image to Library
|
||||
extension UIViewController {
|
||||
|
||||
func save(image: UIImage) {
|
||||
@@ -107,7 +105,6 @@ extension UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Navigation Bar
|
||||
extension UIViewController {
|
||||
|
||||
func setPrefersLargeNavigationTitle(_ title: String, smallScreenToSmallBar: Bool = true) {
|
||||
@@ -129,31 +126,36 @@ extension UIViewController {
|
||||
switch style {
|
||||
case .large:
|
||||
if #available(iOS 11.0, *) {
|
||||
self.navigationController?.navigationBar.prefersLargeTitles = true
|
||||
self.navigationItem.largeTitleDisplayMode = .always
|
||||
}
|
||||
case .small:
|
||||
if #available(iOS 11.0, *) {
|
||||
self.navigationItem.largeTitleDisplayMode = .never
|
||||
}
|
||||
}
|
||||
case .stork:
|
||||
if #available(iOS 11.0, *) {
|
||||
self.navigationItem.largeTitleDisplayMode = .never
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
|
||||
var topSafeArea: CGFloat {
|
||||
return self.view.topSafeArea
|
||||
}
|
||||
|
||||
var bottomSafeArea: CGFloat {
|
||||
return self.view.bottomSafeArea
|
||||
var safeArea: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
return self.view.safeAreaInsets
|
||||
} else {
|
||||
return UIEdgeInsets.zero
|
||||
}
|
||||
}
|
||||
|
||||
var navigationBarHeight: CGFloat {
|
||||
return self.navigationController?.navigationBar.frame.height ?? 0
|
||||
}
|
||||
|
||||
var statusBarHeight: CGFloat {
|
||||
static var statusBarHeight: CGFloat {
|
||||
return UIApplication.shared.statusBarFrame.height
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,17 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
// MARK: - layout
|
||||
public extension UIView {
|
||||
|
||||
var viewController: UIViewController? {
|
||||
get {
|
||||
if let nextResponder = self.next as? UIViewController { return nextResponder }
|
||||
else if let nextResponder = self.next as? UIView { return nextResponder.viewController }
|
||||
else { return nil }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIView {
|
||||
|
||||
var topSafeArea: CGFloat {
|
||||
@@ -49,21 +59,16 @@ public extension UIView {
|
||||
}
|
||||
|
||||
func setEqualsFrameFromBounds(_ view: UIView, withWidthFactor widthFactor: CGFloat = 1, maxWidth: CGFloat? = nil, withHeightFactor heightFactor: CGFloat = 1, maxHeight: CGFloat? = nil, withCentering: Bool = false) {
|
||||
|
||||
self.setEqualsFrameFromBounds(view.bounds, withWidthFactor: widthFactor, maxWidth: maxWidth, withHeightFactor: heightFactor, maxHeight: maxHeight, withCentering: withCentering)
|
||||
}
|
||||
|
||||
func setEqualsFrameFromBounds(_ bounds: CGRect, withWidthFactor widthFactor: CGFloat = 1, maxWidth: CGFloat? = nil, withHeightFactor heightFactor: CGFloat = 1, maxHeight: CGFloat? = nil, withCentering: Bool = false) {
|
||||
|
||||
var width = bounds.width * widthFactor
|
||||
if maxWidth != nil {
|
||||
width.setIfMore(when: maxWidth!)
|
||||
}
|
||||
if maxWidth != nil { width.setIfMore(when: maxWidth!) }
|
||||
|
||||
var height = bounds.height * heightFactor
|
||||
if maxHeight != nil {
|
||||
height.setIfMore(when: maxHeight!)
|
||||
}
|
||||
if maxHeight != nil { height.setIfMore(when: maxHeight!) }
|
||||
|
||||
self.frame = CGRect.init(x: 0, y: 0, width: width, height: height)
|
||||
|
||||
@@ -74,27 +79,19 @@ public extension UIView {
|
||||
}
|
||||
|
||||
func setEqualsBoundsFromSuperview(customWidth: CGFloat? = nil, customHeight: CGFloat? = nil) {
|
||||
|
||||
if self.superview == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if self.superview == nil { return }
|
||||
self.frame = CGRect.init(origin: CGPoint.zero, size: self.superview!.frame.size)
|
||||
|
||||
if customWidth != nil {
|
||||
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: customWidth!, height: self.frame.height))
|
||||
}
|
||||
|
||||
if customHeight != nil {
|
||||
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: self.frame.width, height: customHeight!))
|
||||
}
|
||||
}
|
||||
|
||||
func resize(newWidth width: CGFloat) {
|
||||
func resize(width: CGFloat) {
|
||||
let relativeFactor = self.frame.width / self.frame.height
|
||||
if relativeFactor.isNaN {
|
||||
return
|
||||
}
|
||||
if relativeFactor.isNaN { return }
|
||||
self.frame = CGRect.init(
|
||||
x: self.frame.origin.x,
|
||||
y: self.frame.origin.y,
|
||||
@@ -103,11 +100,9 @@ public extension UIView {
|
||||
)
|
||||
}
|
||||
|
||||
func resize(newHeight height: CGFloat) {
|
||||
func resize(height: CGFloat) {
|
||||
let relativeFactor = self.frame.width / self.frame.height
|
||||
if relativeFactor.isNaN {
|
||||
return
|
||||
}
|
||||
if relativeFactor.isNaN { return }
|
||||
self.frame = CGRect.init(
|
||||
x: self.frame.origin.x,
|
||||
y: self.frame.origin.y,
|
||||
@@ -116,6 +111,10 @@ public extension UIView {
|
||||
)
|
||||
}
|
||||
|
||||
func setYCenteringFromSuperview() {
|
||||
self.center.y = (self.superview?.frame.height ?? 0) / 2
|
||||
}
|
||||
|
||||
func setXCenteringFromSuperview() {
|
||||
self.center.x = (self.superview?.frame.width ?? 0) / 2
|
||||
}
|
||||
@@ -148,29 +147,19 @@ public extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - convertToImage
|
||||
public extension UIView {
|
||||
|
||||
func convertToImage() -> UIImage {
|
||||
return UIImage.drawFromView(view: self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - gradeView
|
||||
public extension UIView {
|
||||
|
||||
func addGrade(alpha: CGFloat, color: UIColor = UIColor.black) -> UIView {
|
||||
let gradeView = UIView.init()
|
||||
gradeView.alpha = 0
|
||||
self.addSubview(gradeView)
|
||||
SPConstraintsAssistent.setEqualSizeConstraint(gradeView, superVuew: self)
|
||||
SPConstraints.setEqualSize(gradeView, superVuew: self)
|
||||
gradeView.alpha = alpha
|
||||
gradeView.backgroundColor = color
|
||||
return gradeView
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - shadow
|
||||
extension UIView {
|
||||
|
||||
func setShadow(
|
||||
@@ -249,7 +238,6 @@ extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - animation
|
||||
extension UIView {
|
||||
|
||||
func addCornerRadiusAnimation(to: CGFloat, duration: CFTimeInterval) {
|
||||
@@ -276,9 +264,12 @@ extension UIView {
|
||||
self.isHidden = true
|
||||
})
|
||||
}
|
||||
|
||||
func removeAllAnimations() {
|
||||
self.layer.removeAllAnimations()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - corner radius
|
||||
extension UIView {
|
||||
|
||||
func roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
|
||||
|
||||
+1
-1
@@ -213,7 +213,7 @@ public class SPPermissionDialogController: SPBaseViewController {
|
||||
self.bottomLabel.sizeToFit()
|
||||
self.bottomLabel.setWidth(bottomLabelWidth)
|
||||
self.bottomLabel.center.x = size.width / 2
|
||||
self.bottomLabel.frame.bottomYPosition = size.height - self.bottomSafeArea - 30
|
||||
self.bottomLabel.frame.bottomYPosition = size.height - self.safeArea.bottom - 30
|
||||
self.bottomLabel.setShadowOffsetForLetters(blurRadius: 3, widthOffset: 0, heightOffset: 0, opacity: 0.18)
|
||||
|
||||
let bottomLabelAlpha: CGFloat = SPDevice.Orientation.isPortrait ? 1 : 0
|
||||
|
||||
@@ -40,5 +40,9 @@ public struct SPNativeStyleKit {
|
||||
static let midGray = UIColor.init(hex: "C7C7CC")
|
||||
static let gray = UIColor.init(hex: "8E8E93")
|
||||
static let black = UIColor.init(hex: "000000")
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ public enum SPSeparatorInsetStyle {
|
||||
public enum SPNavigationTitleStyle {
|
||||
case large
|
||||
case small
|
||||
case stork
|
||||
}
|
||||
|
||||
public enum SPSystemApp {
|
||||
|
||||
+4
-14
@@ -26,7 +26,7 @@ class SPAppStoreActionButton: SPDownloadingButton {
|
||||
var style: Style = .base {
|
||||
didSet {
|
||||
|
||||
self.setTitleColorForNoramlAndHightlightedStates(color: self.baseColor)
|
||||
self.setTitleColor(self.baseColor)
|
||||
self.setTitle(self.titleLabel?.text, for: UIControl.State.normal)
|
||||
|
||||
switch self.style {
|
||||
@@ -38,14 +38,14 @@ class SPAppStoreActionButton: SPDownloadingButton {
|
||||
case .main:
|
||||
self.backgroundColor = self.baseColor
|
||||
self.layer.borderWidth = 0
|
||||
self.setTitleColorForNoramlAndHightlightedStates(color: UIColor.white)
|
||||
self.setTitleColor(UIColor.white)
|
||||
self.titleLabel?.font = UIFont.system(type: .Bold, size: 14)
|
||||
self.contentEdgeInsets = UIEdgeInsets.init(top: 6, left: 15, bottom: 6, right: 15)
|
||||
break
|
||||
case .buyInStore:
|
||||
self.backgroundColor = self.baseColor
|
||||
self.layer.borderWidth = 0
|
||||
self.setTitleColorForNoramlAndHightlightedStates(color: UIColor.white)
|
||||
self.setTitleColor(UIColor.white)
|
||||
self.titleLabel?.font = UIFont.system(type: .Bold, size: 14)
|
||||
self.contentEdgeInsets = UIEdgeInsets.init(top: 8, left: 15, bottom: 8, right: 15)
|
||||
break
|
||||
@@ -73,17 +73,7 @@ class SPAppStoreActionButton: SPDownloadingButton {
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
override func commonInit() {
|
||||
self.style = .base
|
||||
self.layer.masksToBounds = true
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPDownloadingButton: UIButton {
|
||||
class SPDownloadingButton: SPButton {
|
||||
|
||||
let activityIndicatorView = UIActivityIndicatorView.init()
|
||||
var isFrameRounded: Bool = false
|
||||
|
||||
@@ -71,7 +71,5 @@ class SPPlayCircleButton: UIButton {
|
||||
case pause
|
||||
case stop
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
public class SPRoundButton: SPRoundFrameButton {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
|
||||
private func commonInit() {
|
||||
self.backgroundColor = UIColor.white
|
||||
self.layer.borderWidth = 0
|
||||
}
|
||||
}
|
||||
|
||||
public class SPRoundLineButton: SPRoundFrameButton {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
|
||||
private func commonInit() {
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.layer.borderWidth = 2
|
||||
self.layer.borderColor = UIColor(hue: 0,
|
||||
saturation: 0,
|
||||
brightness: 100,
|
||||
alpha: 0.5).cgColor
|
||||
self.layer.borderColor = UIColor.init(white: 1, alpha: 0.5).cgColor
|
||||
}
|
||||
}
|
||||
|
||||
public class SPRoundFrameButton: UIButton {
|
||||
|
||||
override public func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
let minSide = min(self.frame.width, self.frame.height)
|
||||
self.layer.cornerRadius = minSide / 2
|
||||
self.clipsToBounds = true
|
||||
}
|
||||
}
|
||||
+12
-3
@@ -59,7 +59,7 @@ class SPBaseTableViewController: SPStatusBarManagerTableViewController {
|
||||
|
||||
func updateLayout(with size: CGSize) {
|
||||
let layoutIfShowKeyboard = {
|
||||
let height = size.height - (self.keyboardSize?.height ?? 0) - self.topSafeArea
|
||||
let height = size.height - (self.keyboardSize?.height ?? 0) - self.safeArea.top
|
||||
self.emptyProposeView?.frame = CGRect.init(
|
||||
x: 0, y: 0,
|
||||
width: size.width * self.emptyProposeViewWidthFactor,
|
||||
@@ -69,7 +69,7 @@ class SPBaseTableViewController: SPStatusBarManagerTableViewController {
|
||||
}
|
||||
|
||||
let layoutIfNotShowKeyboard = {
|
||||
let height = size.height - self.topSafeArea - self.bottomSafeArea
|
||||
let height = size.height - self.safeArea.top - self.safeArea.bottom
|
||||
self.emptyProposeView?.frame = CGRect.init(
|
||||
x: 0, y: 0,
|
||||
width: size.width * self.emptyProposeViewWidthFactor,
|
||||
@@ -93,7 +93,7 @@ class SPBaseTableViewController: SPStatusBarManagerTableViewController {
|
||||
} else {
|
||||
self.activityIndicatorView.center = CGPoint.init(
|
||||
x: size.width / 2,
|
||||
y: (size.height - self.topSafeArea - self.bottomSafeArea) / 2
|
||||
y: (size.height - self.safeArea.top - self.safeArea.bottom) / 2
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -299,4 +299,13 @@ class SPBaseTableViewController: SPStatusBarManagerTableViewController {
|
||||
@objc func dismiss(sender: Any) {
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
func addHideButton(title: String) {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(
|
||||
title: title,
|
||||
style: UIBarButtonItem.Style.done,
|
||||
target: self,
|
||||
action: #selector(self.dismiss(sender:))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+17
-4
@@ -59,14 +59,14 @@ public class SPBaseViewController: SPStatusBarManagerViewController {
|
||||
|
||||
func updateLayout(with size: CGSize) {
|
||||
|
||||
var contentHeight = size.height - self.topSafeArea
|
||||
var contentHeight = size.height - self.safeArea.top
|
||||
if self.isShowKeyboard {
|
||||
contentHeight = contentHeight - (self.keyboardSize?.height ?? 0)
|
||||
} else {
|
||||
contentHeight = contentHeight - self.bottomSafeArea
|
||||
contentHeight = contentHeight - self.safeArea.bottom
|
||||
}
|
||||
|
||||
let centerYPosition = self.topSafeArea + (contentHeight / 2)
|
||||
let centerYPosition = self.safeArea.top + (contentHeight / 2)
|
||||
|
||||
if self.activityIndicatorLayoutWithSafeArea {
|
||||
self.activityIndicatorView.center = CGPoint.init(
|
||||
@@ -88,7 +88,7 @@ public class SPBaseViewController: SPStatusBarManagerViewController {
|
||||
self.emptyProposeView?.frame = CGRect.init(
|
||||
x: 0, y: 0,
|
||||
width: emptyProposeViewWidth,
|
||||
height: (size.height - self.topSafeArea - self.bottomSafeArea) * self.emptyProposeViewHeightFactor
|
||||
height: (size.height - self.safeArea.top - self.safeArea.bottom) * self.emptyProposeViewHeightFactor
|
||||
)
|
||||
self.emptyProposeView?.center = CGPoint.init(
|
||||
x: size.width / 2,
|
||||
@@ -103,6 +103,19 @@ public class SPBaseViewController: SPStatusBarManagerViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func addHideButton(title: String) {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(
|
||||
title: title,
|
||||
style: UIBarButtonItem.Style.done,
|
||||
target: self,
|
||||
action: #selector(self.dismiss(sender:))
|
||||
)
|
||||
}
|
||||
|
||||
@objc func dismiss(sender: Any) {
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
//MARK: - keyboard
|
||||
var isShowKeyboard: Bool = false
|
||||
var keyboardSize: CGSize? = nil
|
||||
|
||||
+1
-1
@@ -166,7 +166,7 @@ class SPConfirmActionViewController: UIViewController {
|
||||
self.addSubview(self.label)
|
||||
|
||||
self.button.setTitle("Сancel", for: .normal)
|
||||
self.button.setTitleColorForNoramlAndHightlightedStates(color: SPNativeStyleKit.Colors.blue)
|
||||
self.button.setTitleColor(SPNativeStyleKit.Colors.blue)
|
||||
self.button.titleLabel?.font = UIFont.system(type: .DemiBold, size: 16)
|
||||
self.addSubview(self.button)
|
||||
}
|
||||
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
class SPHiderViewController: SPBaseViewController {
|
||||
|
||||
let backgroundView = SPGradeBlurView.init()
|
||||
|
||||
private var durationAnimation: TimeInterval = 0.3
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.statusBar = .light
|
||||
|
||||
self.view.backgroundColor = UIColor.clear
|
||||
|
||||
self.backgroundView.setGradeColor(UIColor.black)
|
||||
self.activityIndicatorView.alpha = 0
|
||||
self.backgroundView.setGradeAlpha(0, blurRaius: 0)
|
||||
self.view.addSubview(self.backgroundView)
|
||||
|
||||
self.activityIndicatorView.startAnimating()
|
||||
self.view.addSubview(self.activityIndicatorView)
|
||||
|
||||
self.updateLayout(with: self.view.frame.size)
|
||||
}
|
||||
|
||||
override func updateLayout(with size: CGSize) {
|
||||
self.backgroundView.frame = CGRect.init(origin: CGPoint.zero, size: size)
|
||||
self.activityIndicatorView.center = CGPoint.init(x: size.width / 2, y: size.height / 2)
|
||||
}
|
||||
|
||||
func present(on viewController: UIViewController) {
|
||||
self.modalTransitionStyle = .crossDissolve
|
||||
self.modalPresentationStyle = .overCurrentContext
|
||||
viewController.present(self, animated: false) {
|
||||
SPAnimation.animate(self.durationAnimation, animations: {
|
||||
self.backgroundView.setGradeAlpha(0.5, blurRaius: 8)
|
||||
})
|
||||
SPAnimation.animate(self.durationAnimation * 1.5, animations: {
|
||||
self.activityIndicatorView.alpha = 1
|
||||
}, delay: self.durationAnimation / 1.5)
|
||||
}
|
||||
}
|
||||
|
||||
override func dismiss() {
|
||||
SPAnimation.animate(self.durationAnimation / 1.2, animations: {
|
||||
self.activityIndicatorView.alpha = 0
|
||||
})
|
||||
SPAnimation.animate(self.durationAnimation, animations: {
|
||||
self.backgroundView.setGradeAlpha(0, blurRaius: 0)
|
||||
}, delay: 0) {
|
||||
self.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
-13
@@ -183,19 +183,6 @@ extension SPNativeTableViewController {
|
||||
|
||||
}
|
||||
|
||||
//MARK: - hide button
|
||||
extension SPNativeTableViewController {
|
||||
|
||||
func addHideButton(title: String) {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(
|
||||
title: title,
|
||||
style: UIBarButtonItem.Style.done,
|
||||
target: self,
|
||||
action: #selector(self.dismiss(sender:))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - manage spaces
|
||||
extension SPNativeTableViewController {
|
||||
|
||||
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
class SPProgressLoadingViewController: SPBaseViewController {
|
||||
|
||||
let titleLabel = UILabel.init()
|
||||
let subtitleLabel = UILabel.init()
|
||||
let progressView = UIProgressView.init(progressViewStyle: UIProgressView.Style.default)
|
||||
let commentLabel = UILabel.init()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.view.backgroundColor = SPNativeStyleKit.Colors.white
|
||||
|
||||
self.titleLabel.text = "Title"
|
||||
self.titleLabel.font = UIFont.system(type: UIFont.BoldType.Medium, size: 23)
|
||||
self.titleLabel.textColor = SPNativeStyleKit.Colors.black
|
||||
self.titleLabel.numberOfLines = 1
|
||||
self.view.addSubview(self.titleLabel)
|
||||
|
||||
self.subtitleLabel.text = "Subtitle..."
|
||||
self.subtitleLabel.setCenteringAlignment()
|
||||
self.subtitleLabel.font = UIFont.system(type: UIFont.BoldType.Regular, size: 13)
|
||||
self.subtitleLabel.textColor = SPNativeStyleKit.Colors.gray
|
||||
self.subtitleLabel.numberOfLines = 1
|
||||
self.view.addSubview(self.subtitleLabel)
|
||||
|
||||
self.progressView.progress = 0
|
||||
self.view.addSubview(self.progressView)
|
||||
|
||||
self.activityIndicatorView.color = SPNativeStyleKit.Colors.gray
|
||||
self.activityIndicatorView.startAnimating()
|
||||
self.view.addSubview(self.activityIndicatorView)
|
||||
|
||||
self.commentLabel.text = "Comment"
|
||||
self.commentLabel.setCenteringAlignment()
|
||||
self.commentLabel.font = UIFont.system(type: UIFont.BoldType.Regular, size: 11)
|
||||
self.commentLabel.textColor = SPNativeStyleKit.Colors.gray
|
||||
self.commentLabel.numberOfLines = 1
|
||||
self.view.addSubview(self.commentLabel)
|
||||
|
||||
self.updateLayout(with: self.view.frame.size)
|
||||
}
|
||||
|
||||
func set(progress: Float) {
|
||||
self.progressView.setProgress(progress, animated: true)
|
||||
}
|
||||
|
||||
override func updateLayout(with size: CGSize) {
|
||||
self.titleLabel.sizeToFit()
|
||||
self.titleLabel.center.x = size.width / 2
|
||||
|
||||
self.subtitleLabel.sizeToFit()
|
||||
self.subtitleLabel.center.x = size.width / 2
|
||||
|
||||
self.progressView.setWidth(size.width * 0.55)
|
||||
self.progressView.center.x = size.width / 2
|
||||
|
||||
self.activityIndicatorView.sizeToFit()
|
||||
self.activityIndicatorView.center.x = size.width / 2
|
||||
|
||||
let allHeight = self.titleLabel.frame.height + 5 + self.subtitleLabel.frame.height + 30 + self.progressView.frame.height + 30 + self.activityIndicatorView.frame.height
|
||||
|
||||
self.titleLabel.frame.origin.y = (size.height - allHeight - self.topSafeArea) / 2
|
||||
self.subtitleLabel.frame.origin.y = self.titleLabel.frame.bottomYPosition + 5
|
||||
self.progressView.frame.origin.y = self.subtitleLabel.frame.bottomYPosition + 30
|
||||
self.activityIndicatorView.frame.origin.y = self.progressView.frame.bottomYPosition + 30
|
||||
|
||||
self.commentLabel.sizeToFit()
|
||||
self.commentLabel.center.x = size.width / 2
|
||||
self.commentLabel.frame.origin.y = size.height - (self.view.bottomSafeArea + self.commentLabel.frame.height + 5)
|
||||
}
|
||||
}
|
||||
+18
-5
@@ -83,7 +83,7 @@ class SPProposeViewController: SPBaseViewController {
|
||||
self.areaView.layoutSubviews()
|
||||
self.areaView.sizeToFit()
|
||||
self.areaView.frame.origin.x = self.space
|
||||
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (UIWindow.bottomSafeArea / 2) - self.space
|
||||
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (self.bottomSafeArea / 2) - self.space
|
||||
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class SPProposeViewController: SPBaseViewController {
|
||||
SPAnimationSpring.animate(self.animationDuration, animations: {
|
||||
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
|
||||
|
||||
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (UIWindow.bottomSafeArea / 2) - self.space
|
||||
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (self.bottomSafeArea / 2) - self.space
|
||||
}, spring: 1,
|
||||
velocity: 1,
|
||||
options: .transitionCurlUp)
|
||||
@@ -134,7 +134,7 @@ class SPProposeViewController: SPBaseViewController {
|
||||
|
||||
let returnAreaViewToPoint = {
|
||||
SPAnimationSpring.animate(self.animationDuration, animations: {
|
||||
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (UIWindow.bottomSafeArea / 2) - self.space
|
||||
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (self.bottomSafeArea / 2) - self.space
|
||||
}, spring: 1,
|
||||
velocity: 1,
|
||||
options: .transitionCurlDown,
|
||||
@@ -173,7 +173,7 @@ class SPProposeViewController: SPBaseViewController {
|
||||
let titleLabel = UILabel()
|
||||
let subtitleLabel = UILabel()
|
||||
let imageView = SPDownloadingImageView()
|
||||
let button = SPNativeOS11Button()
|
||||
let button = SPNativeLargeButton()
|
||||
let closeButton = SPSystemIconButton(type: SPSystemIconType.close)
|
||||
|
||||
var imageSideSize: CGFloat = 160
|
||||
@@ -206,7 +206,7 @@ class SPProposeViewController: SPBaseViewController {
|
||||
self.addSubview(self.imageView)
|
||||
|
||||
self.button.titleLabel?.font = UIFont.system(type: UIFont.BoldType.Medium, size: 15)
|
||||
self.button.setTitleColorForNoramlAndHightlightedStates(color: SPNativeStyleKit.Colors.black)
|
||||
self.button.setTitleColor(SPNativeStyleKit.Colors.black)
|
||||
self.button.backgroundColor = UIColor.init(hex: "D4D3DB")
|
||||
self.addSubview(self.button)
|
||||
|
||||
@@ -266,4 +266,17 @@ class SPProposeViewController: SPBaseViewController {
|
||||
var image: UIImage?
|
||||
var complection: (_ isConfirmed: Bool)->()
|
||||
}
|
||||
|
||||
var bottomSafeArea: CGFloat {
|
||||
var bottomSafeArea: CGFloat = 0
|
||||
if let window = UIApplication.shared.keyWindow {
|
||||
if #available(iOS 11.0, *) {
|
||||
bottomSafeArea = window.safeAreaInsets.bottom
|
||||
}
|
||||
} else {
|
||||
bottomSafeArea = 0
|
||||
}
|
||||
|
||||
return bottomSafeArea
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import UIKit
|
||||
|
||||
struct SPRootViewController {
|
||||
|
||||
static var controller: UIViewController? {
|
||||
return UIApplication.shared.keyWindow?.rootViewController
|
||||
}
|
||||
|
||||
static func set(_ rootController: UIViewController, animatable: Bool = true) {
|
||||
|
||||
rootController.view.frame = UIScreen.main.bounds
|
||||
|
||||
let replaceRootViewController = {
|
||||
UIApplication.shared.keyWindow?.rootViewController = rootController
|
||||
}
|
||||
|
||||
if animatable {
|
||||
UIView.transition(
|
||||
with: UIApplication.shared.keyWindow ?? UIWindow(),
|
||||
duration: 0.5,
|
||||
options: UIView.AnimationOptions.transitionCrossDissolve,
|
||||
animations: {
|
||||
replaceRootViewController()
|
||||
}, completion: nil)
|
||||
} else {
|
||||
replaceRootViewController()
|
||||
}
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
+4
-4
@@ -28,7 +28,7 @@ class SPWelcomeViewController: SPBaseViewController {
|
||||
let subtitleLabel = UILabel()
|
||||
let descriptionLabel = UILabel()
|
||||
let commentLabel = UILabel()
|
||||
let button = SPNativeOS11Button()
|
||||
let button = SPNativeLargeButton()
|
||||
|
||||
private var data: SPWelcomeData
|
||||
private var views: [UIView] = []
|
||||
@@ -134,7 +134,7 @@ class SPWelcomeViewController: SPBaseViewController {
|
||||
|
||||
let sideSpace: CGFloat = size.width * 0.112
|
||||
|
||||
self.imageView.frame = CGRect.init(x: sideSpace, y: self.topSafeArea + (size.height * 0.1), width: 84, height: 84)
|
||||
self.imageView.frame = CGRect.init(x: sideSpace, y: self.safeArea.top + (size.height * 0.1), width: 84, height: 84)
|
||||
|
||||
let space: CGFloat = size.height * 0.025
|
||||
|
||||
@@ -148,7 +148,7 @@ class SPWelcomeViewController: SPBaseViewController {
|
||||
self.descriptionLabel.sizeToFit()
|
||||
|
||||
self.button.sizeToFit()
|
||||
self.button.frame.origin.y = size.height - self.bottomSafeArea - space - self.button.frame.height
|
||||
self.button.frame.origin.y = size.height - self.safeArea.bottom - space - self.button.frame.height
|
||||
self.button.setWidth(size.width - sideSpace * 2)
|
||||
self.button.center.x = size.width / 2
|
||||
|
||||
@@ -214,5 +214,5 @@ struct SPWelcomeData {
|
||||
var backgroundColor: UIColor = UIColor.white
|
||||
var textColor: UIColor = UIColor.black
|
||||
var statusBarStyle: SPStatusBar = .dark
|
||||
var complection: (_ button: SPNativeOS11Button) -> () = { _ in }
|
||||
var complection: (_ button: SPNativeLargeButton) -> () = { _ in }
|
||||
}
|
||||
|
||||
-201
@@ -1,201 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
final class SPStorkPresentationController: UIPresentationController, UIGestureRecognizerDelegate {
|
||||
|
||||
var indicatorView = SPStorkIndicatorView()
|
||||
var isSwipeToDismissEnabled: Bool = true
|
||||
var transitioningDelegate: SPStorkTransitioningDelegate?
|
||||
private var pan: UIPanGestureRecognizer?
|
||||
|
||||
private var scaleFactor: CGFloat {
|
||||
return 349 / 375
|
||||
}
|
||||
|
||||
private var topSpace: CGFloat {
|
||||
if presentingViewController.statusBarHeight < 25 {
|
||||
return 30
|
||||
} else {
|
||||
return presentingViewController.statusBarHeight
|
||||
}
|
||||
}
|
||||
|
||||
private var transform: CGAffineTransform {
|
||||
let translate: CGFloat = ((self.containerView?.frame.height ?? 0) * (1 - self.scaleFactor)) / 2
|
||||
return CGAffineTransform.identity.scaledBy(x: scaleFactor, y: scaleFactor).translatedBy(x: 0, y: -translate + topSpace)
|
||||
}
|
||||
|
||||
private var alpha: CGFloat {
|
||||
return 0.51
|
||||
}
|
||||
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
coordinator.animate(alongsideTransition: { (contex) in
|
||||
self.updateLayout()
|
||||
}, completion: nil)
|
||||
}
|
||||
|
||||
private func updateLayout() {
|
||||
guard let presentedView = self.presentedView else { return }
|
||||
self.indicatorView.sizeToFit()
|
||||
self.indicatorView.frame.origin.y = 12
|
||||
self.indicatorView.center.x = presentedView.frame.width / 2
|
||||
}
|
||||
|
||||
override var frameOfPresentedViewInContainerView: CGRect {
|
||||
if let containerView = self.containerView {
|
||||
let yOffset = topSpace + 13
|
||||
return CGRect(x: 0, y: yOffset, width: containerView.bounds.width, height: containerView.bounds.height - yOffset)
|
||||
} else {
|
||||
return .zero
|
||||
}
|
||||
}
|
||||
|
||||
override func presentationTransitionWillBegin() {
|
||||
self.updateLayout()
|
||||
self.indicatorView.style = .arrow
|
||||
|
||||
guard let containerView = self.containerView, let presentedView = self.presentedView, let presentingView = self.presentingViewController.view else { return }
|
||||
|
||||
presentedView.addSubview(self.indicatorView)
|
||||
|
||||
presentedView.addCornerRadiusAnimation(to: 10, duration: 0.6)
|
||||
presentedView.layer.masksToBounds = true
|
||||
presentingView.layer.cornerRadius = 10
|
||||
presentingView.layer.masksToBounds = true
|
||||
containerView.addSubview(presentedView)
|
||||
|
||||
guard let transitionCoordinator = self.presentingViewController.transitionCoordinator else { return }
|
||||
transitionCoordinator.animateAlongsideTransition(in: self.presentingViewController.view, animation: { _ in
|
||||
self.presentingViewController.view.transform = self.transform
|
||||
self.presentingViewController.view.alpha = self.alpha
|
||||
})
|
||||
}
|
||||
|
||||
override func presentationTransitionDidEnd(_ completed: Bool) {
|
||||
if self.isSwipeToDismissEnabled {
|
||||
self.pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
|
||||
self.pan!.delegate = self
|
||||
self.pan!.maximumNumberOfTouches = 1
|
||||
self.pan!.cancelsTouchesInView = false
|
||||
self.presentedViewController.view.addGestureRecognizer(pan!)
|
||||
}
|
||||
}
|
||||
|
||||
override func dismissalTransitionWillBegin() {
|
||||
guard let transitionCoordinator = presentingViewController.transitionCoordinator else { return }
|
||||
|
||||
transitionCoordinator.animate(alongsideTransition: { _ in
|
||||
self.presentingViewController.view.alpha = 1
|
||||
})
|
||||
|
||||
transitionCoordinator.animateAlongsideTransition(in: presentingViewController.view, animation: { _ in
|
||||
self.presentingViewController.view.transform = CGAffineTransform.identity
|
||||
})
|
||||
|
||||
self.presentingViewController.view.addCornerRadiusAnimation(to: 0, duration: 0.6)
|
||||
}
|
||||
|
||||
override func dismissalTransitionDidEnd(_ completed: Bool) {
|
||||
if let presentingView = self.presentingViewController.view {
|
||||
presentingView.layer.cornerRadius = 0
|
||||
presentingView.layer.masksToBounds = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SPStorkPresentationController {
|
||||
|
||||
@objc fileprivate func handlePan(gestureRecognizer: UIPanGestureRecognizer) {
|
||||
guard gestureRecognizer.isEqual(pan), self.isSwipeToDismissEnabled else {
|
||||
return
|
||||
}
|
||||
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
/*UIView.animate(
|
||||
withDuration: 0.25,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 1,
|
||||
initialSpringVelocity: 1,
|
||||
options: .curveEaseOut,
|
||||
animations: {
|
||||
self.indicatorView.transform = CGAffineTransform.init(scaleX: 1.2, y: 1.2)
|
||||
})*/
|
||||
self.indicatorView.style = .line
|
||||
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: containerView)
|
||||
case .changed:
|
||||
if self.isSwipeToDismissEnabled {
|
||||
let translation = gestureRecognizer.translation(in: presentedView)
|
||||
updatePresentedViewForTranslation(inVerticalDirection: translation.y)
|
||||
} else {
|
||||
gestureRecognizer.setTranslation(.zero, in: presentedView)
|
||||
}
|
||||
case .ended:
|
||||
let translation = gestureRecognizer.translation(in: presentedView).y
|
||||
if translation >= 240 {
|
||||
presentedViewController.dismiss(animated: true, completion: nil)
|
||||
} else {
|
||||
self.indicatorView.style = .arrow
|
||||
UIView.animate(
|
||||
withDuration: 0.6,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 1,
|
||||
initialSpringVelocity: 1,
|
||||
options: .curveEaseOut,
|
||||
animations: {
|
||||
self.presentedView?.transform = .identity
|
||||
self.presentingViewController.view.transform = self.transform
|
||||
self.presentingViewController.view.alpha = self.alpha
|
||||
})
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePresentedViewForTranslation(inVerticalDirection translation: CGFloat) {
|
||||
|
||||
let elasticThreshold: CGFloat = 120
|
||||
let translationFactor: CGFloat = 1 / 2
|
||||
|
||||
if translation >= 0 {
|
||||
let translationForModal: CGFloat = {
|
||||
if translation >= elasticThreshold {
|
||||
let frictionLength = translation - elasticThreshold
|
||||
let frictionTranslation = 30 * atan(frictionLength / 120) + frictionLength / 10
|
||||
return frictionTranslation + (elasticThreshold * translationFactor)
|
||||
} else {
|
||||
return translation * translationFactor
|
||||
}
|
||||
}()
|
||||
|
||||
self.presentedView?.transform = CGAffineTransform(translationX: 0, y: translationForModal)
|
||||
|
||||
let factor = 1 + (translationForModal / 6000)
|
||||
self.presentingViewController.view.transform = self.transform.scaledBy(x: factor, y: factor)
|
||||
self.presentingViewController.view.alpha = self.alpha + ((factor - 1) * 15)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
class SPAligmentView: UIView {
|
||||
|
||||
var minSpace: CGFloat = 0
|
||||
var maxItemSideSize: CGFloat?
|
||||
var spaceFactor: CGFloat = 0.07
|
||||
var aliment: Aliment
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
self.aliment = .vertical
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
init(aliment: Aliment) {
|
||||
self.aliment = aliment
|
||||
super.init(frame: CGRect.zero)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let countViews: CGFloat = CGFloat(self.subviews.count)
|
||||
|
||||
var itemWidth: CGFloat = 0
|
||||
var itemHeight: CGFloat = 0
|
||||
var space: CGFloat = 0
|
||||
|
||||
switch self.aliment {
|
||||
case .horizontal:
|
||||
itemHeight = self.frame.height
|
||||
space = self.frame.width * self.spaceFactor
|
||||
if space < self.minSpace {
|
||||
space = self.minSpace
|
||||
}
|
||||
let spaceForButton = self.frame.width - (space * (countViews - 1))
|
||||
itemWidth = spaceForButton / countViews
|
||||
if self.maxItemSideSize != nil {
|
||||
if (itemWidth > self.maxItemSideSize!) {
|
||||
itemWidth = self.maxItemSideSize!
|
||||
if countViews > 1 {
|
||||
space = (self.frame.width - (itemWidth * countViews)) / (countViews - 1)
|
||||
} else {
|
||||
space = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
case .vertical:
|
||||
itemWidth = self.frame.width
|
||||
space = self.frame.height * self.spaceFactor
|
||||
if space < self.minSpace {
|
||||
space = self.minSpace
|
||||
}
|
||||
let spaceForButton = self.frame.height - (space * (countViews - 1))
|
||||
itemHeight = spaceForButton / countViews
|
||||
if self.maxItemSideSize != nil {
|
||||
if (itemHeight > self.maxItemSideSize!) {
|
||||
itemHeight = self.maxItemSideSize!
|
||||
if countViews > 1 {
|
||||
space = (self.frame.height - (itemHeight * countViews)) / (countViews - 1)
|
||||
} else {
|
||||
space = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var xPoint: CGFloat = 0
|
||||
var yPoint: CGFloat = 0
|
||||
for (index, view) in self.subviews.enumerated() {
|
||||
switch self.aliment {
|
||||
case .horizontal:
|
||||
xPoint = CGFloat(index) * (itemWidth + space)
|
||||
case .vertical:
|
||||
yPoint = CGFloat(index) * (itemHeight + space)
|
||||
}
|
||||
view.frame = CGRect.init(
|
||||
x: xPoint, y: yPoint,
|
||||
width: itemWidth,
|
||||
height: itemHeight
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum Aliment {
|
||||
case vertical
|
||||
case horizontal
|
||||
}
|
||||
}
|
||||
|
||||
class SPCenteringAligmentView: SPAligmentView {
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
if self.subviews.count == 0 {
|
||||
return
|
||||
}
|
||||
var itemHeight: CGFloat = self.subviews[0].frame.height
|
||||
var itemWidth: CGFloat = self.subviews[0].frame.width
|
||||
let countViews: CGFloat = CGFloat(self.subviews.count)
|
||||
|
||||
if countViews < 3 {
|
||||
switch self.aliment {
|
||||
case .horizontal:
|
||||
switch self.subviews.count {
|
||||
case 1:
|
||||
self.subviews[0].center.x = self.frame.width / 2
|
||||
case 2:
|
||||
let allFreeSpace = self.frame.width - (itemWidth * countViews)
|
||||
var space = allFreeSpace / 2
|
||||
if space < self.minSpace {
|
||||
space = self.minSpace
|
||||
itemWidth = (self.frame.width - (space * 2)) / 2
|
||||
}
|
||||
self.subviews[0].frame = CGRect.init(x: space / 2, y: self.subviews[0].frame.origin.y, width: itemWidth, height: self.subviews[0].frame.height)
|
||||
self.subviews[1].frame = CGRect.init(x: space / 2 + itemWidth + space, y: self.subviews[1].frame.origin.y, width: itemWidth, height: self.subviews[1].frame.height)
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .vertical:
|
||||
switch self.subviews.count {
|
||||
case 1:
|
||||
self.subviews[0].center.y = self.frame.height / 2
|
||||
case 2:
|
||||
let allFreeSpace = self.frame.height - (itemHeight * countViews)
|
||||
var space = allFreeSpace / 2
|
||||
if space < self.minSpace {
|
||||
space = self.minSpace
|
||||
itemHeight = (self.frame.height - (space * 2)) / 2
|
||||
}
|
||||
self.subviews[0].frame = CGRect.init(x: self.subviews[0].frame.origin.x, y: space / 2, width: self.subviews[0].frame.width, height: itemHeight)
|
||||
self.subviews[1].frame = CGRect.init(x: self.subviews[1].frame.origin.x, y: (space / 2) + itemHeight + space, width: self.subviews[1].frame.width, height: itemHeight)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SPDinamicAligmentView: UIView {
|
||||
|
||||
var aliment: Aliment
|
||||
|
||||
var itemSideSize: CGFloat = 50
|
||||
var space: CGFloat = 10
|
||||
|
||||
var needSize: CGSize {
|
||||
get {
|
||||
self.layoutSubviews()
|
||||
switch aliment {
|
||||
case .horizontal:
|
||||
return CGSize.init(width: ((self.subviews.last?.frame.origin.x) ?? 0) + ((self.subviews.last?.frame.width) ?? 0), height: (self.subviews.last?.frame.height) ?? 0)
|
||||
case .vertical:
|
||||
return CGSize.init(width: (self.subviews.last?.frame.width) ?? 0, height: (self.subviews.last?.frame.bottomYPosition) ?? 0)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
self.aliment = .vertical
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
init(aliment: Aliment) {
|
||||
self.aliment = aliment
|
||||
super.init(frame: CGRect.zero)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
var xPoint: CGFloat = 0
|
||||
var yPoint: CGFloat = 0
|
||||
var itemWidth: CGFloat = 0
|
||||
var itemHeight: CGFloat = 0
|
||||
|
||||
for (index, view) in self.subviews.enumerated() {
|
||||
switch self.aliment {
|
||||
case .horizontal:
|
||||
xPoint = CGFloat(index) * (self.itemSideSize + space)
|
||||
itemWidth = self.itemSideSize
|
||||
itemHeight = self.frame.height
|
||||
case .vertical:
|
||||
yPoint = CGFloat(index) * (self.itemSideSize + space)
|
||||
itemWidth = self.frame.width
|
||||
itemHeight = self.itemSideSize
|
||||
}
|
||||
view.frame = CGRect.init(
|
||||
x: xPoint, y: yPoint,
|
||||
width: itemWidth,
|
||||
height: itemHeight
|
||||
)
|
||||
}
|
||||
|
||||
var needSideSize: CGFloat = 0
|
||||
let lastView = self.subviews.last
|
||||
if lastView != nil {
|
||||
switch self.aliment {
|
||||
case .horizontal:
|
||||
needSideSize = lastView!.frame.origin.x + lastView!.frame.width
|
||||
self.setWidth(needSideSize)
|
||||
break
|
||||
case .vertical:
|
||||
needSideSize = lastView!.frame.origin.y + lastView!.frame.height
|
||||
self.setHeight(needSideSize)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Aliment {
|
||||
case vertical
|
||||
case horizontal
|
||||
}
|
||||
|
||||
}
|
||||
+4
-19
@@ -21,27 +21,13 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPDownloadingImageView: UIImageView {
|
||||
class SPDownloadingImageView: SPImageView {
|
||||
|
||||
let activityIndiactorView = UIActivityIndicatorView.init()
|
||||
let gradeView = UIView.init()
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
override init(image: UIImage?) {
|
||||
super.init(image: image)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
func commonInit() {
|
||||
override func commonInit() {
|
||||
super.commonInit()
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.layer.masksToBounds = true
|
||||
self.addSubview(self.gradeView)
|
||||
@@ -76,13 +62,12 @@ class SPDownloadingImageView: UIImageView {
|
||||
}
|
||||
}
|
||||
|
||||
func setLoadingMode() {
|
||||
func startLoading() {
|
||||
self.image = nil
|
||||
self.activityIndiactorView.startAnimating()
|
||||
self.gradeView.alpha = 1
|
||||
}
|
||||
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.gradeView.setEqualsBoundsFromSuperview()
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class SPGradientView: UIView {
|
||||
public class SPGradientView: SPView {
|
||||
|
||||
var startColor: UIColor = UIColor.white { didSet { self.updateGradient() }}
|
||||
var endColor: UIColor = UIColor.black { didSet { self.updateGradient() }}
|
||||
@@ -29,17 +29,7 @@ public class SPGradientView: UIView {
|
||||
var startColorPoint: CGPoint = CGPoint.zero { didSet { self.updateGradient() }}
|
||||
var endColorPoint: CGPoint = CGPoint.zero { didSet { self.updateGradient() }}
|
||||
|
||||
var gradientLayer: CAGradientLayer!
|
||||
|
||||
public init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
var gradientLayer: CAGradientLayer = CAGradientLayer()
|
||||
|
||||
public func setStartColorPosition(_ position: Position) {
|
||||
self.startColorPoint = getPointForPosition(position)
|
||||
@@ -49,16 +39,16 @@ public class SPGradientView: UIView {
|
||||
self.endColorPoint = getPointForPosition(position)
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
self.gradientLayer = CAGradientLayer()
|
||||
self.layer.insertSublayer(self.gradientLayer!, at: 0)
|
||||
override func commonInit() {
|
||||
super.commonInit()
|
||||
self.layer.insertSublayer(self.gradientLayer, at: 0)
|
||||
}
|
||||
|
||||
private func updateGradient() {
|
||||
self.gradientLayer!.colors = [startColor.cgColor, endColor.cgColor]
|
||||
self.gradientLayer!.locations = [0.0, 1.0]
|
||||
self.gradientLayer!.startPoint = self.startColorPoint
|
||||
self.gradientLayer!.endPoint = self.endColorPoint
|
||||
self.gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
|
||||
self.gradientLayer.locations = [0.0, 1.0]
|
||||
self.gradientLayer.startPoint = self.startColorPoint
|
||||
self.gradientLayer.endPoint = self.endColorPoint
|
||||
}
|
||||
|
||||
override public func layoutSublayers(of layer: CALayer) {
|
||||
|
||||
@@ -23,6 +23,23 @@ import UIKit
|
||||
|
||||
class SPScrollView: UIScrollView {
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
internal func commonInit() {
|
||||
if #available(iOS 11.0, *) {
|
||||
self.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
self.delaysContentTouches = false
|
||||
}
|
||||
|
||||
override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
if view is UIControl
|
||||
&& !(view is UITextInput)
|
||||
|
||||
@@ -6,6 +6,8 @@ class ViewController: SPStatusBarManagerViewController {
|
||||
super.viewDidLoad()
|
||||
let tap = UITapGestureRecognizer(target: self, action: #selector(self.viewWasTapped))
|
||||
view.addGestureRecognizer(tap)
|
||||
|
||||
print("Tap anymore for present modal controller")
|
||||
}
|
||||
|
||||
@objc func viewWasTapped() {
|
||||
@@ -19,20 +21,29 @@ class ViewController: SPStatusBarManagerViewController {
|
||||
|
||||
class ModalViewController: UIViewController {
|
||||
|
||||
let navBar = SPFakeBarView(style: .stork)
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.view.backgroundColor = UIColor.white
|
||||
self.modalPresentationCapturesStatusBarAppearance = true
|
||||
|
||||
let tap = UITapGestureRecognizer(target: self, action: #selector(self.viewWasTapped))
|
||||
view.addGestureRecognizer(tap)
|
||||
|
||||
self.navBar.titleLabel.text = "Title"
|
||||
self.navBar.leftButton.setTitle("Cancel")
|
||||
self.navBar.leftButton.target {
|
||||
self.dismiss()
|
||||
}
|
||||
self.view.addSubview(self.navBar)
|
||||
}
|
||||
|
||||
@objc func viewWasTapped() {
|
||||
self.dismiss(animated: true, completion: nil)
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return .lightContent
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
# SPStorkController
|
||||
Modal controller as in mail or Apple music application. Similar animation and transition. Now in develop
|
||||
<img src="https://rawcdn.githack.com/IvanVorobei/SPStorkController/90c836ec5649e77fb44ff727d7dad96d2009f3d8/Resources/SPStorkController - Name.svg"/>
|
||||
|
||||
Modal controller as in mail or Apple music application. Similar animation and transition. I tried to repeat all the animations, corner radius and frames. The controller supports gestures and Navigation Bar.
|
||||
|
||||
Preview GIF loading `4mb`. Please, wait
|
||||
|
||||
<img src="https://rawcdn.githack.com/IvanVorobei/SPStorkController/0acd51bbe76ef48611e1bdd408aebb9c7d9b0ae6/resources/gif-mockup.gif" width="500">
|
||||
|
||||
<img src="https://rawcdn.githack.com/IvanVorobei/RequestPermission/6fcd9bdb50a99cea358294999e161dffe55be46f/resources/request-permission - donate.svg"/>
|
||||
<img src="https://rawcdn.githack.com/IvanVorobei/SPStorkController/4b1c91dacc4d3f901fcab5c7efdff256a40c4381/Resources/SPStorkController - Donate.svg"/>
|
||||
|
||||
The project is absolutely free, but but it takes time to support and update it. Your support is very motivating and very important. I often receive emails asking me to update or add functionality. [Small donate](https://money.yandex.ru/to/410012745748312) for a cup of coffee helps to develop the project and make it better
|
||||
|
||||
<img src="https://rawcdn.githack.com/IvanVorobei/RequestPermission/6fcd9bdb50a99cea358294999e161dffe55be46f/resources/request-permission - donate.svg"/>
|
||||
<img src="https://rawcdn.githack.com/IvanVorobei/SPStorkController/4b1c91dacc4d3f901fcab5c7efdff256a40c4381/Resources/SPStorkController - Donate.svg"/>
|
||||
|
||||
## Requirements
|
||||
Swift 4.2. Ready for use on iOS 10+
|
||||
|
||||
## Integration
|
||||
Drop in `source/sparrow` folder to your Xcode project. Make sure to enable `Copy items if needed` and `Create groups`
|
||||
Drop in `Source/Sparrow` folder to your Xcode project. Make sure to enable `Copy items if needed` and `Create groups`.
|
||||
|
||||
Or via CocoaPods:
|
||||
```ruby
|
||||
@@ -24,27 +25,110 @@ pod 'SPStorkController'
|
||||
|
||||
and import library in class:
|
||||
```swift
|
||||
import SparrowKit
|
||||
import SPStorkController
|
||||
```
|
||||
|
||||
## How to use
|
||||
Init controller (please, set background, it maybe clear color) and set `transitioningDelegate`. Use `present` or `dissmiss` funcs:
|
||||
Create controller (please, set background, it maybe clear color) and set `transitioningDelegate` to `SPStorkTransitioningDelegate()`. Use `present` or `dismiss` functions:
|
||||
```swift
|
||||
import SparrowKit
|
||||
import UIKit
|
||||
import SPStorkController
|
||||
|
||||
let controller = UIViewController()
|
||||
let transitionDelegate = SPStorkTransitioningDelegate()
|
||||
controller.transitioningDelegate = transitionDelegate
|
||||
controller.modalPresentationStyle = .custom
|
||||
present(controller, animated: true, completion: nil)
|
||||
class ViewController: UIViewController {
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
let controller = UIViewController()
|
||||
let transitionDelegate = SPStorkTransitioningDelegate()
|
||||
controller.transitioningDelegate = transitionDelegate
|
||||
controller.modalPresentationStyle = .custom
|
||||
self.present(controller, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Parametrs
|
||||
#### Upload in next version, now paramtrs no available
|
||||
|
||||
- Parametr `isSwipeToDismissEnabled` enable dissmiss by swipe gester. Defualt is `true`:
|
||||
|
||||
```swift
|
||||
let transitionDelegate = SPStorkTransitioningDelegate()
|
||||
transitionDelegate.isSwipeToDismissEnabled = true
|
||||
```
|
||||
|
||||
- Parametr `showIndicator` show or hide top arrow indicator. Defualt is `true`:
|
||||
```swift
|
||||
transitionDelegate.showIndicator = true
|
||||
```
|
||||
|
||||
### Add Navigation Bar
|
||||
You may want to add a navigation bar to your modal controller. Since it became impossible to change or customize the native controller in swift 4 (I couldn’t even find a way to change the height of bar), I completely create navigation bar. Visually, it looks real, but it doesn’t execute the necessary functions:
|
||||
|
||||
```swift
|
||||
import UIKit
|
||||
|
||||
class ModalController: UIViewController {
|
||||
|
||||
let navBar = SPFakeBarView(style: .stork)
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.view.backgroundColor = UIColor.white
|
||||
|
||||
self.navBar.titleLabel.text = "Title"
|
||||
self.navBar.leftButton.setTitle("Cancel")
|
||||
self.navBar.leftButton.target {
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
self.view.addSubview(self.navBar)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You only need to add a navigation bar to the main view, it will automatically layout. Use style `.stork` in init `SPFakeBarView`. It is image preview with Navigation Bar and without it:
|
||||
|
||||
<img src="https://rawcdn.githack.com/IvanVorobei/SPStorkController/916cfef888b3e70ca45d1b8b26fba1947421632b/Recources/SPStorkController - Banner.jpg"/>
|
||||
|
||||
For use `SPFakeBarView` you need install additional pod:
|
||||
|
||||
```ruby
|
||||
pod 'SparrowKit'
|
||||
```
|
||||
|
||||
### Work with UIScrollView
|
||||
|
||||
If you use `UIScrollView` (or UITableView & UICollectionView) on your controller, I recommend making it more interactive. When the scroll reaches the top position, the controller will interactively drag down, simulating a closing animation. To do this, set the delegate and in the function `scrollViewDidScroll` call:
|
||||
|
||||
```swift
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
SPStorkController.scrollViewDidScroll(scrollView)
|
||||
}
|
||||
```
|
||||
|
||||
## My projects
|
||||
|
||||
Here I would like to offer you my other projects
|
||||
|
||||
### SPPermission
|
||||
Project [SPPermission](https://github.com/IvanVorobei/SPPermission) for managing permissions with the customizable visual effects. Beautiful dialog increases the chance of approval (which is important when we request notification). Simple control of this module saves you hours of development. You can start using [this project](https://github.com/IvanVorobei/SPPermission) with just two lines of code and easy customization!
|
||||
|
||||
<img src="https://rawcdn.githack.com/IvanVorobei/RequestPermission/fb53d20f152a3e76e053e6af529306611fb794f0/resources/request-permission - mockup_preview.gif" width="500">
|
||||
|
||||
### SparrowKit
|
||||
The `SPStorkController` additional use [SparrowKit](https://github.com/IvanVorobei/SparrowKit) library. Also in library you can find [SPPermission](https://github.com/IvanVorobei/SPPermission) and other useful extensions. For install via CocoaPods use:
|
||||
|
||||
```ruby
|
||||
pod 'SparrowKit'
|
||||
```
|
||||
|
||||
## License
|
||||
`SPStorkController` is released under the MIT license. Check LICENSE.md for details
|
||||
|
||||
## Contact
|
||||
If you need develop application or UI, write me to hello@ivanvorobei.by. I am develop design and ios apps. I am use `swift`. If you want to ask for more functionality, you should create a new issue
|
||||
If you need develop application or UI, write me to hello@ivanvorobei.by. I am develop design and ios apps. I am use `swift`. If you want to ask for more functionality, you should create a new issue.
|
||||
|
||||
[hello.ivanvorobei.by](https://hello.ivanvorobei.by) & [ivanvorobei.by](https://hello.ivanvorobei.by)
|
||||
|
||||
My apps [in AppStore](https://itunes.apple.com/us/developer/polina-zubarik/id1434528595) & [in AppStore 2](https://itunes.apple.com/us/developer/mikalai-varabei/id1435792103)
|
||||
My apps in AppStore: [first account](https://itunes.apple.com/us/developer/polina-zubarik/id1434528595) & [second account](https://itunes.apple.com/us/developer/mikalai-varabei/id1435792103)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SPStorkController"
|
||||
s.version = "1.0.4"
|
||||
s.version = "1.1"
|
||||
s.summary = "Modal controller as mail or Apple music application"
|
||||
s.homepage = "https://github.com/IvanVorobei/SPStorkController"
|
||||
s.source = { :git => "https://github.com/IvanVorobei/SPStorkController.git", :tag => s.version }
|
||||
@@ -13,7 +13,7 @@ Pod::Spec.new do |s|
|
||||
s.platform = :ios
|
||||
s.ios.deployment_target = "10.0"
|
||||
|
||||
s.dependency 'SparrowKit', '~> 1.0.0'
|
||||
s.source_files = "Source/StorkController/**/*.swift"
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
public struct SPStorkController {
|
||||
|
||||
static func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if let controller = self.controller(for: scrollView) {
|
||||
if let presentationController = controller.presentationController as? SPStorkPresentationController {
|
||||
let translation = -(scrollView.contentOffset.y + scrollView.contentInset.top)
|
||||
if translation >= 0 {
|
||||
scrollView.subviews.forEach {
|
||||
$0.transform = CGAffineTransform(translationX: 0, y: -translation)
|
||||
}
|
||||
if presentationController.pan?.state != UIGestureRecognizer.State.changed {
|
||||
presentationController.scrollViewDidScroll(translation)
|
||||
}
|
||||
} else {
|
||||
presentationController.scrollViewDidScroll(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private func controller(for view: UIView) -> UIViewController? {
|
||||
var nextResponder = view.next
|
||||
while nextResponder != nil && !(nextResponder! is UIViewController) {
|
||||
nextResponder = nextResponder!.next
|
||||
}
|
||||
return nextResponder as? UIViewController
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
|
||||
var isPresentedAsStork: Bool {
|
||||
return transitioningDelegate is SPStorkTransitioningDelegate
|
||||
&& modalPresentationStyle == .custom
|
||||
&& presentingViewController != nil
|
||||
}
|
||||
}
|
||||
+25
-18
@@ -19,27 +19,34 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import SystemConfiguration
|
||||
import UIKit
|
||||
|
||||
public struct SPInternetConnection {
|
||||
|
||||
static var isOpen: Bool {
|
||||
var zeroAddress = sockaddr_in()
|
||||
zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
|
||||
zeroAddress.sin_family = sa_family_t(AF_INET)
|
||||
let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
|
||||
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
|
||||
SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
|
||||
}
|
||||
final class SPStorkDismissingAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
|
||||
guard let presentedViewController = transitionContext.viewController(forKey: .from) else {
|
||||
return
|
||||
}
|
||||
var flags = SCNetworkReachabilityFlags()
|
||||
if !SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) {
|
||||
return false
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
|
||||
UIView.animate(
|
||||
withDuration: transitionDuration(using: transitionContext),
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 1,
|
||||
initialSpringVelocity: 1,
|
||||
options: .curveEaseIn,
|
||||
animations: {
|
||||
presentedViewController.view.frame = offscreenFrame
|
||||
}) { finished in
|
||||
transitionContext.completeTransition(finished)
|
||||
}
|
||||
let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
|
||||
let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
|
||||
return (isReachable && !needsConnection)
|
||||
}
|
||||
|
||||
private init() {}
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
return 0.6
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
class SPStorkIndicatorView: UIView {
|
||||
|
||||
var style: Style = .line {
|
||||
didSet {
|
||||
switch self.style {
|
||||
case .line:
|
||||
self.animate {
|
||||
self.leftView.transform = .identity
|
||||
self.rightView.transform = .identity
|
||||
}
|
||||
case .arrow:
|
||||
self.animate {
|
||||
let angle = CGFloat(20 * Float.pi / 180)
|
||||
self.leftView.transform = CGAffineTransform.init(rotationAngle: angle)
|
||||
self.rightView.transform = CGAffineTransform.init(rotationAngle: -angle)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private var leftView: UIView = UIView()
|
||||
private var rightView: UIView = UIView()
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.addSubview(self.leftView)
|
||||
self.addSubview(self.rightView)
|
||||
self.leftView.backgroundColor = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1)
|
||||
self.rightView.backgroundColor = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func sizeToFit() {
|
||||
super.sizeToFit()
|
||||
self.frame = CGRect.init(x: self.frame.origin.x, y: self.frame.origin.y, width: 36, height: 13)
|
||||
|
||||
let height: CGFloat = 5
|
||||
let correction = height / 2
|
||||
|
||||
self.leftView.frame = CGRect.init(x: 0, y: 0, width: self.frame.width / 2 + correction, height: height)
|
||||
self.leftView.center.y = self.frame.height / 2
|
||||
self.leftView.layer.cornerRadius = min(self.leftView.frame.width, self.leftView.frame.height) / 2
|
||||
|
||||
self.rightView.frame = CGRect.init(x: self.frame.width / 2 - correction, y: 0, width: self.frame.width / 2 + correction, height: height)
|
||||
self.rightView.center.y = self.frame.height / 2
|
||||
self.rightView.layer.cornerRadius = min(self.leftView.frame.width, self.leftView.frame.height) / 2
|
||||
}
|
||||
|
||||
private func animate(animations: @escaping (() -> Void)) {
|
||||
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.beginFromCurrentState, .curveEaseOut], animations: {
|
||||
animations()
|
||||
})
|
||||
}
|
||||
|
||||
enum Style {
|
||||
case arrow
|
||||
case line
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
class SPStorkPresentationController: UIPresentationController, UIGestureRecognizerDelegate {
|
||||
|
||||
var isSwipeToDismissEnabled: Bool = true
|
||||
var showIndicator: Bool = true
|
||||
var transitioningDelegate: SPStorkTransitioningDelegate?
|
||||
var pan: UIPanGestureRecognizer?
|
||||
|
||||
private var indicatorView = SPStorkIndicatorView()
|
||||
private var gradeView: UIView = UIView()
|
||||
private let snapshotViewContainer = UIView()
|
||||
private var snapshotView: UIView?
|
||||
private let backgroundView = UIView()
|
||||
|
||||
private var snapshotViewTopConstraint: NSLayoutConstraint?
|
||||
private var snapshotViewWidthConstraint: NSLayoutConstraint?
|
||||
private var snapshotViewAspectRatioConstraint: NSLayoutConstraint?
|
||||
private var workGester: Bool = false
|
||||
private var startDismissing: Bool = false
|
||||
|
||||
private var topSpace: CGFloat {
|
||||
let statusBarHeight: CGFloat = UIApplication.shared.statusBarFrame.height
|
||||
return (statusBarHeight < 25) ? 30 : statusBarHeight
|
||||
}
|
||||
|
||||
private var alpha: CGFloat {
|
||||
return 0.51
|
||||
}
|
||||
|
||||
private var cornerRadius: CGFloat {
|
||||
return 10
|
||||
}
|
||||
|
||||
private var scaleForPresentingView: CGFloat {
|
||||
guard let containerView = containerView else { return 0 }
|
||||
let factor = 1 - (topSpace * 2 / containerView.frame.height)
|
||||
return factor
|
||||
}
|
||||
|
||||
override var frameOfPresentedViewInContainerView: CGRect {
|
||||
guard let containerView = containerView else { return .zero }
|
||||
let yOffset: CGFloat = topSpace + 13
|
||||
return CGRect(x: 0, y: yOffset, width: containerView.bounds.width, height: containerView.bounds.height - yOffset)
|
||||
}
|
||||
|
||||
override func presentationTransitionWillBegin() {
|
||||
super.presentationTransitionWillBegin()
|
||||
|
||||
guard let containerView = self.containerView, let presentedView = self.presentedView, let window = containerView.window else { return }
|
||||
|
||||
if self.showIndicator {
|
||||
presentedView.addSubview(self.indicatorView)
|
||||
}
|
||||
self.updateLayoutIndicator()
|
||||
self.indicatorView.style = .arrow
|
||||
self.gradeView.alpha = 0
|
||||
|
||||
let initialFrame: CGRect = presentingViewController.isPresentedAsStork ? presentingViewController.view.frame : containerView.bounds
|
||||
|
||||
containerView.insertSubview(self.snapshotViewContainer, belowSubview: presentedViewController.view)
|
||||
self.snapshotViewContainer.frame = initialFrame
|
||||
self.updateSnapshot()
|
||||
self.backgroundView.backgroundColor = UIColor.black
|
||||
self.backgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||
containerView.insertSubview(self.backgroundView, belowSubview: self.snapshotViewContainer)
|
||||
NSLayoutConstraint.activate([
|
||||
self.backgroundView.topAnchor.constraint(equalTo: window.topAnchor),
|
||||
self.backgroundView.leftAnchor.constraint(equalTo: window.leftAnchor),
|
||||
self.backgroundView.rightAnchor.constraint(equalTo: window.rightAnchor),
|
||||
self.backgroundView.bottomAnchor.constraint(equalTo: window.bottomAnchor)
|
||||
])
|
||||
|
||||
let transformForSnapshotView = CGAffineTransform.identity
|
||||
.translatedBy(x: 0, y: -snapshotViewContainer.frame.origin.y)
|
||||
.translatedBy(x: 0, y: self.topSpace)
|
||||
.translatedBy(x: 0, y: -snapshotViewContainer.frame.height / 2)
|
||||
.scaledBy(x: scaleForPresentingView, y: scaleForPresentingView)
|
||||
.translatedBy(x: 0, y: snapshotViewContainer.frame.height / 2)
|
||||
|
||||
self.addCornerRadiusAnimation(for: self.snapshotView, cornerRadius: self.cornerRadius, duration: 0.6)
|
||||
self.snapshotView?.layer.masksToBounds = true
|
||||
if #available(iOS 11.0, *) {
|
||||
presentedView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
}
|
||||
presentedView.layer.cornerRadius = self.cornerRadius
|
||||
presentedView.layer.masksToBounds = true
|
||||
|
||||
var rootSnapshotView: UIView?
|
||||
var rootSnapshotRoundedView: UIView?
|
||||
|
||||
if presentingViewController.isPresentedAsStork {
|
||||
guard let rootController = presentingViewController.presentingViewController, let snapshotView = rootController.view.snapshotView(afterScreenUpdates: false) else { return }
|
||||
|
||||
containerView.insertSubview(snapshotView, aboveSubview: self.backgroundView)
|
||||
snapshotView.frame = initialFrame
|
||||
snapshotView.transform = transformForSnapshotView
|
||||
snapshotView.alpha = self.alpha
|
||||
snapshotView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotView.layer.masksToBounds = true
|
||||
rootSnapshotView = snapshotView
|
||||
|
||||
let snapshotRoundedView = UIView()
|
||||
snapshotRoundedView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotRoundedView.layer.masksToBounds = true
|
||||
containerView.insertSubview(snapshotRoundedView, aboveSubview: snapshotView)
|
||||
snapshotRoundedView.frame = initialFrame
|
||||
snapshotRoundedView.transform = transformForSnapshotView
|
||||
rootSnapshotRoundedView = snapshotRoundedView
|
||||
}
|
||||
|
||||
presentedViewController.transitionCoordinator?.animate(
|
||||
alongsideTransition: { [weak self] context in
|
||||
guard let `self` = self else { return }
|
||||
self.snapshotView?.transform = transformForSnapshotView
|
||||
self.gradeView.alpha = self.alpha
|
||||
}, completion: { _ in
|
||||
self.snapshotView?.transform = .identity
|
||||
rootSnapshotView?.removeFromSuperview()
|
||||
rootSnapshotRoundedView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
override func presentationTransitionDidEnd(_ completed: Bool) {
|
||||
super.presentationTransitionDidEnd(completed)
|
||||
guard let containerView = containerView else { return }
|
||||
self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView
|
||||
self.snapshotViewContainer.transform = .identity
|
||||
self.snapshotViewContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.snapshotViewContainer.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
|
||||
self.updateSnapshotAspectRatio()
|
||||
|
||||
if self.isSwipeToDismissEnabled {
|
||||
self.pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
|
||||
self.pan!.delegate = self
|
||||
self.pan!.maximumNumberOfTouches = 1
|
||||
self.pan!.cancelsTouchesInView = false
|
||||
self.presentedViewController.view.addGestureRecognizer(self.pan!)
|
||||
}
|
||||
}
|
||||
|
||||
override func dismissalTransitionWillBegin() {
|
||||
super.dismissalTransitionWillBegin()
|
||||
guard let containerView = containerView else { return }
|
||||
self.startDismissing = true
|
||||
|
||||
let initialFrame: CGRect = presentingViewController.isPresentedAsStork ? presentingViewController.view.frame : containerView.bounds
|
||||
|
||||
let initialTransform = CGAffineTransform.identity
|
||||
.translatedBy(x: 0, y: -initialFrame.origin.y)
|
||||
.translatedBy(x: 0, y: self.topSpace)
|
||||
.translatedBy(x: 0, y: -initialFrame.height / 2)
|
||||
.scaledBy(x: scaleForPresentingView, y: scaleForPresentingView)
|
||||
.translatedBy(x: 0, y: initialFrame.height / 2)
|
||||
|
||||
self.snapshotViewTopConstraint?.isActive = false
|
||||
self.snapshotViewWidthConstraint?.isActive = false
|
||||
self.snapshotViewAspectRatioConstraint?.isActive = false
|
||||
self.snapshotViewContainer.translatesAutoresizingMaskIntoConstraints = true
|
||||
self.snapshotViewContainer.frame = initialFrame
|
||||
self.snapshotViewContainer.transform = initialTransform
|
||||
|
||||
let finalCornerRadius = presentingViewController.isPresentedAsStork ? self.cornerRadius : 0
|
||||
let finalTransform: CGAffineTransform = .identity
|
||||
|
||||
self.addCornerRadiusAnimation(for: self.snapshotView, cornerRadius: finalCornerRadius, duration: 0.6)
|
||||
|
||||
var rootSnapshotView: UIView?
|
||||
var rootSnapshotRoundedView: UIView?
|
||||
|
||||
if presentingViewController.isPresentedAsStork {
|
||||
guard let rootController = presentingViewController.presentingViewController, let snapshotView = rootController.view.snapshotView(afterScreenUpdates: false) else { return }
|
||||
|
||||
containerView.insertSubview(snapshotView, aboveSubview: backgroundView)
|
||||
snapshotView.frame = initialFrame
|
||||
snapshotView.transform = initialTransform
|
||||
rootSnapshotView = snapshotView
|
||||
snapshotView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotView.layer.masksToBounds = true
|
||||
|
||||
let snapshotRoundedView = UIView()
|
||||
snapshotRoundedView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotRoundedView.layer.masksToBounds = true
|
||||
snapshotRoundedView.backgroundColor = UIColor.black.withAlphaComponent(1 - self.alpha)
|
||||
containerView.insertSubview(snapshotRoundedView, aboveSubview: snapshotView)
|
||||
snapshotRoundedView.frame = initialFrame
|
||||
snapshotRoundedView.transform = initialTransform
|
||||
rootSnapshotRoundedView = snapshotRoundedView
|
||||
}
|
||||
|
||||
presentedViewController.transitionCoordinator?.animate(
|
||||
alongsideTransition: { [weak self] context in
|
||||
guard let `self` = self else { return }
|
||||
self.snapshotView?.transform = .identity
|
||||
self.snapshotViewContainer.transform = finalTransform
|
||||
self.gradeView.alpha = 0
|
||||
}, completion: { _ in
|
||||
rootSnapshotView?.removeFromSuperview()
|
||||
rootSnapshotRoundedView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
override func dismissalTransitionDidEnd(_ completed: Bool) {
|
||||
super.dismissalTransitionDidEnd(completed)
|
||||
guard let containerView = containerView else { return }
|
||||
|
||||
self.backgroundView.removeFromSuperview()
|
||||
self.snapshotView?.removeFromSuperview()
|
||||
self.snapshotViewContainer.removeFromSuperview()
|
||||
|
||||
let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
presentedViewController.view.frame = offscreenFrame
|
||||
presentedViewController.view.transform = .identity
|
||||
}
|
||||
}
|
||||
|
||||
extension SPStorkPresentationController {
|
||||
|
||||
@objc func handlePan(gestureRecognizer: UIPanGestureRecognizer) {
|
||||
guard gestureRecognizer.isEqual(pan), self.isSwipeToDismissEnabled else { return }
|
||||
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
self.workGester = true
|
||||
self.indicatorView.style = .line
|
||||
self.presentingViewController.view.layer.removeAllAnimations()
|
||||
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: containerView)
|
||||
case .changed:
|
||||
self.workGester = true
|
||||
if self.isSwipeToDismissEnabled {
|
||||
let translation = gestureRecognizer.translation(in: presentedView)
|
||||
self.updatePresentedViewForTranslation(inVerticalDirection: translation.y)
|
||||
} else {
|
||||
gestureRecognizer.setTranslation(.zero, in: presentedView)
|
||||
}
|
||||
case .ended:
|
||||
self.workGester = false
|
||||
let translation = gestureRecognizer.translation(in: presentedView).y
|
||||
if translation >= 240 {
|
||||
presentedViewController.dismiss(animated: true, completion: nil)
|
||||
} else {
|
||||
self.indicatorView.style = .arrow
|
||||
UIView.animate(
|
||||
withDuration: 0.6,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 1,
|
||||
initialSpringVelocity: 1,
|
||||
options: [.curveEaseOut, .allowUserInteraction],
|
||||
animations: {
|
||||
self.snapshotView?.transform = .identity
|
||||
self.presentedView?.transform = .identity
|
||||
})
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ translation: CGFloat) {
|
||||
if !self.workGester {
|
||||
self.updatePresentedViewForTranslation(inVerticalDirection: translation)
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePresentedViewForTranslation(inVerticalDirection translation: CGFloat) {
|
||||
if self.startDismissing { return }
|
||||
|
||||
let elasticThreshold: CGFloat = 120
|
||||
let translationFactor: CGFloat = 1 / 2
|
||||
|
||||
if translation >= 0 {
|
||||
let translationForModal: CGFloat = {
|
||||
if translation >= elasticThreshold {
|
||||
let frictionLength = translation - elasticThreshold
|
||||
let frictionTranslation = 30 * atan(frictionLength / 120) + frictionLength / 10
|
||||
return frictionTranslation + (elasticThreshold * translationFactor)
|
||||
} else {
|
||||
return translation * translationFactor
|
||||
}
|
||||
}()
|
||||
|
||||
self.presentedView?.transform = CGAffineTransform(translationX: 0, y: translationForModal)
|
||||
|
||||
if !self.presentingViewController.isPresentedAsStork {
|
||||
let factor = 1 + (translationForModal / 6000)
|
||||
self.snapshotView?.transform = CGAffineTransform.init(scaleX: factor, y: factor)
|
||||
self.gradeView.alpha = self.alpha - ((factor - 1) * 15)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SPStorkPresentationController {
|
||||
|
||||
override func containerViewWillLayoutSubviews() {
|
||||
super.containerViewWillLayoutSubviews()
|
||||
guard let containerView = containerView else { return }
|
||||
self.updateSnapshotAspectRatio()
|
||||
if presentedViewController.view.isDescendant(of: containerView) {
|
||||
UIView.animate(withDuration: 0.1) { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
coordinator.animate(alongsideTransition: { contex in
|
||||
self.updateLayoutIndicator()
|
||||
}, completion: { [weak self] _ in
|
||||
self?.updateSnapshotAspectRatio()
|
||||
self?.updateSnapshot()
|
||||
})
|
||||
}
|
||||
|
||||
private func updateLayoutIndicator() {
|
||||
guard let presentedView = self.presentedView else { return }
|
||||
self.indicatorView.style = .line
|
||||
self.indicatorView.sizeToFit()
|
||||
self.indicatorView.frame.origin.y = 12
|
||||
self.indicatorView.center.x = presentedView.frame.width / 2
|
||||
}
|
||||
|
||||
private func updateSnapshot() {
|
||||
guard let currentSnapshotView = presentingViewController.view.snapshotView(afterScreenUpdates: true) else { return }
|
||||
self.snapshotView?.removeFromSuperview()
|
||||
self.snapshotViewContainer.addSubview(currentSnapshotView)
|
||||
self.constraints(view: currentSnapshotView, to: self.snapshotViewContainer)
|
||||
self.snapshotView = currentSnapshotView
|
||||
self.gradeView.removeFromSuperview()
|
||||
self.gradeView.backgroundColor = UIColor.black
|
||||
self.snapshotView!.addSubview(self.gradeView)
|
||||
self.constraints(view: self.gradeView, to: self.snapshotView!)
|
||||
}
|
||||
|
||||
private func updateSnapshotAspectRatio() {
|
||||
guard let containerView = containerView, snapshotViewContainer.translatesAutoresizingMaskIntoConstraints == false else { return }
|
||||
|
||||
self.snapshotViewTopConstraint?.isActive = false
|
||||
self.snapshotViewWidthConstraint?.isActive = false
|
||||
self.snapshotViewAspectRatioConstraint?.isActive = false
|
||||
|
||||
let snapshotReferenceSize = presentingViewController.view.frame.size
|
||||
let aspectRatio = snapshotReferenceSize.width / snapshotReferenceSize.height
|
||||
|
||||
self.snapshotViewTopConstraint = snapshotViewContainer.topAnchor.constraint(equalTo: containerView.topAnchor, constant: self.topSpace)
|
||||
self.snapshotViewWidthConstraint = snapshotViewContainer.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: scaleForPresentingView)
|
||||
self.snapshotViewAspectRatioConstraint = snapshotViewContainer.widthAnchor.constraint(equalTo: snapshotViewContainer.heightAnchor, multiplier: aspectRatio)
|
||||
|
||||
self.snapshotViewTopConstraint?.isActive = true
|
||||
self.snapshotViewWidthConstraint?.isActive = true
|
||||
self.snapshotViewAspectRatioConstraint?.isActive = true
|
||||
}
|
||||
|
||||
private func constraints(view: UIView, to superView: UIView) {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
view.topAnchor.constraint(equalTo: superView.topAnchor),
|
||||
view.leftAnchor.constraint(equalTo: superView.leftAnchor),
|
||||
view.rightAnchor.constraint(equalTo: superView.rightAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: superView.bottomAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
private func addCornerRadiusAnimation(for view: UIView?, cornerRadius: CGFloat, duration: CFTimeInterval) {
|
||||
guard let view = view else { return }
|
||||
let animation = CABasicAnimation(keyPath:"cornerRadius")
|
||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
|
||||
animation.fromValue = view.layer.cornerRadius
|
||||
animation.toValue = cornerRadius
|
||||
animation.duration = duration
|
||||
view.layer.add(animation, forKey: "cornerRadius")
|
||||
view.layer.cornerRadius = cornerRadius
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// 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
|
||||
|
||||
final class SPStorkPresentingAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
|
||||
guard let presentedViewController = transitionContext.viewController(forKey: .to) else {
|
||||
return
|
||||
}
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
containerView.addSubview(presentedViewController.view)
|
||||
presentedViewController.view.frame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
|
||||
let finalFrameForPresentedView = transitionContext.finalFrame(for: presentedViewController)
|
||||
|
||||
UIView.animate(
|
||||
withDuration: transitionDuration(using: transitionContext),
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 1,
|
||||
initialSpringVelocity: 1,
|
||||
options: .curveEaseOut,
|
||||
animations: {
|
||||
presentedViewController.view.frame = finalFrameForPresentedView
|
||||
}, completion: { finished in
|
||||
transitionContext.completeTransition(finished)
|
||||
})
|
||||
}
|
||||
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
return 0.6
|
||||
}
|
||||
}
|
||||
|
||||
+13
-16
@@ -21,25 +21,22 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPScrollViewController: SPBaseViewController {
|
||||
public final class SPStorkTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
|
||||
|
||||
let scrollView = SPScrollView()
|
||||
var isSwipeToDismissEnabled: Bool = true
|
||||
var showIndicator: Bool = true
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
self.view.backgroundColor = SPNativeStyleKit.Colors.customGray
|
||||
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.showsVerticalScrollIndicator = true
|
||||
self.scrollView.indicatorStyle = .black
|
||||
self.view.addSubview(self.scrollView)
|
||||
|
||||
super.viewDidLoad()
|
||||
public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
|
||||
let controller = SPStorkPresentationController(presentedViewController: presented, presenting: presenting)
|
||||
controller.transitioningDelegate = self
|
||||
return controller
|
||||
}
|
||||
|
||||
override func updateLayout(with size: CGSize) {
|
||||
self.scrollView.frame = CGRect.init(origin: .zero, size: size)
|
||||
self.scrollView.contentSize = CGSize.init(width: size.width, height: size.height * 2)
|
||||
super.updateLayout(with: size)
|
||||
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
return SPStorkPresentingAnimationController()
|
||||
}
|
||||
|
||||
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
return SPStorkDismissingAnimationController()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user