Compare commits

...

21 Commits

Author SHA1 Message Date
Ivan Vorobei 883ade3052 Update README.md 2018-12-06 15:08:41 +03:00
Ivan Vorobei 44837098ea Update to version 1.1 2018-12-06 15:05:47 +03:00
Ivan Vorobei 42063749ed Update README.md 2018-12-04 12:20:52 +03:00
Ivan Vorobei 464df2eb5c Update README.md 2018-12-04 12:19:31 +03:00
Ivan Vorobei 6f48df5761 Update README.md 2018-12-04 12:10:54 +03:00
Ivan Vorobei e331793afc Update README.md 2018-12-04 12:10:23 +03:00
Ivan Vorobei 82152b698a Update README.md 2018-12-03 14:33:11 +03:00
Ivan Vorobei 75eaaec598 Update Readme 2018-12-03 10:53:25 +03:00
Ivan Vorobei d2cc2e424c Update README.md 2018-12-02 21:13:33 +03:00
Ivan Vorobei 0ac35b4494 Update README.md 2018-12-02 21:13:18 +03:00
Ivan Vorobei 6c2600dea8 Update README.md 2018-12-01 23:36:18 +03:00
Ivan Vorobei 26d7422216 Update README.md 2018-12-01 23:35:01 +03:00
Ivan Vorobei a4a02b598f Update README.md 2018-11-30 20:54:40 +03:00
Ivan Vorobei 0309bb2e0e Update example and Readme 2018-11-30 20:43:35 +03:00
Ivan Vorobei 4e0507f132 Update README.md 2018-11-30 20:33:04 +03:00
Ivan Vorobei 8e33ff7614 Add donate banner 2018-11-30 20:32:36 +03:00
Ivan Vorobei b7ab9e0327 Update README.md 2018-11-30 20:30:39 +03:00
Ivan Vorobei 4129f23d00 Add header banner 2018-11-30 20:29:57 +03:00
Ivan Vorobei 91b01d83d9 Update Readme 2018-11-30 20:26:33 +03:00
Ivan Vorobei 4eda691bd0 Update README.md 2018-11-30 20:25:06 +03:00
Ivan Vorobei 0889352299 Update Readme 2018-11-30 20:18:38 +03:00
94 changed files with 2730 additions and 2500 deletions
File diff suppressed because it is too large Load Diff
@@ -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)
])
}
}
@@ -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)
}
}
@@ -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()
}
}
@@ -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()
}
}
@@ -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
}
}
@@ -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,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()
}
}
}
@@ -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() {}
}
@@ -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))
}
}
@@ -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()
}
}
}
@@ -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)
}
}
@@ -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)
}
}
@@ -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()
}
}
@@ -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)
@@ -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
@@ -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 {
@@ -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)
@@ -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)
@@ -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
}
}
@@ -28,9 +28,10 @@ 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)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
delay: 0,
@@ -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)
}
}
}
@@ -50,30 +50,34 @@ class SPStorkIndicatorView: UIView {
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 {
@@ -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
}
}
@@ -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)
@@ -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)
}
@@ -23,34 +23,17 @@ import UIKit
extension CGRect {
static var displayFrame: CGRect {
let screenSize = UIScreen.main.bounds
return CGRect.init(origin: .zero, size: screenSize.size)
}
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
@@ -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
@@ -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
}
}
@@ -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) {
@@ -282,7 +270,6 @@ extension UIView {
}
}
// MARK: - corner radius
extension UIView {
func roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
@@ -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 {
@@ -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
}
}
@@ -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:))
)
}
}
@@ -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
@@ -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)
}
@@ -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)
}
}
}
@@ -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 {
@@ -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)
}
}
@@ -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() {}
}
@@ -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 }
}
@@ -1,215 +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.style = .line
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: CGFloat = 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
if !self.isPresentingControllerUseStork {
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
if self.presentingViewController.view.frame.origin != CGPoint.zero {
self.presentingViewController.view.frame = CGRect.displayFrame
}
})
if !self.isPresentingControllerUseStork {
self.presentingViewController.view.addCornerRadiusAnimation(to: 0, duration: 0.6)
}
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
if !self.isPresentingControllerUseStork {
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:
self.indicatorView.style = .line
self.presentingViewController.view.removeAllAnimations()
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, .allowUserInteraction],
animations: {
self.presentedView?.transform = .identity
if !self.isPresentingControllerUseStork {
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)
if !self.isPresentingControllerUseStork {
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)
}
}
}
private var isPresentingControllerUseStork: Bool {
let controller = self.presentingViewController
return controller.transitioningDelegate is SPStorkTransitioningDelegate
&& controller.modalPresentationStyle == .custom
&& controller.presentingViewController != nil
}
}
@@ -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
}
}
@@ -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)
+13 -2
View File
@@ -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
}
}
+97 -15
View File
@@ -1,21 +1,22 @@
# SPStorkController
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
<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,29 +25,110 @@ pod 'SPStorkController'
and import library in class:
```swift
import SparrowKit
import SPStorkController
```
## How to use
Create controller (please, set background, it maybe clear color) and set `transitioningDelegate` to `SPStorkTransitioningDelegate()`. Use `present` or `dismiss` functions:
```swift
let controller = UIViewController()
controller.transitioningDelegate = SPStorkTransitioningDelegate()
controller.modalPresentationStyle = .custom
present(controller, animated: true, completion: nil)
import UIKit
import SPStorkController
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 couldnt even find a way to change the height of bar), I completely create navigation bar. Visually, it looks real, but it doesnt 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
Project [SPPermission](https://github.com/IvanVorobei/SPPermission) about 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 with just two lines of code and easy customization!
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)
+2 -2
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "SPStorkController"
s.version = "1.0.6"
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.1'
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
}
}
@@ -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
}
}
@@ -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()
}
}