Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 883ade3052 | |||
| 44837098ea | |||
| 42063749ed | |||
| 464df2eb5c | |||
| 6f48df5761 | |||
| e331793afc | |||
| 82152b698a | |||
| 75eaaec598 | |||
| d2cc2e424c | |||
| 0ac35b4494 | |||
| 6c2600dea8 | |||
| 26d7422216 | |||
| a4a02b598f |
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
+12
-8
@@ -21,14 +21,18 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UITableViewController {
|
||||
public struct SPConstraints {
|
||||
|
||||
func refreshManually() {
|
||||
self.refreshControl?.beginRefreshing()
|
||||
self.tableView.setContentOffset(
|
||||
CGPoint.init(
|
||||
x: 0,
|
||||
y: self.tableView.contentOffset.y - (self.refreshControl?.frame.size.height ?? 0)
|
||||
), animated: true)
|
||||
static func setEqualSize(_ view: UIView, superVuew: UIView) {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
view.topAnchor.constraint(equalTo: superVuew.topAnchor),
|
||||
view.leftAnchor.constraint(equalTo: superVuew.leftAnchor),
|
||||
view.rightAnchor.constraint(equalTo: superVuew.rightAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: superVuew.bottomAnchor)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+7
-1
@@ -24,7 +24,13 @@ import UIKit
|
||||
extension UIVisualEffectView {
|
||||
|
||||
convenience init(style: UIBlurEffect.Style) {
|
||||
let effect = UIBlurEffect(style: .extraLight)
|
||||
let effect = UIBlurEffect(style: style)
|
||||
self.init(effect: effect)
|
||||
}
|
||||
|
||||
convenience init(vibrancy style: UIBlurEffect.Style) {
|
||||
let effect = UIBlurEffect(style: style)
|
||||
let vibrancyEffect = UIVibrancyEffect(blurEffect: effect)
|
||||
self.init(effect: vibrancyEffect)
|
||||
}
|
||||
}
|
||||
|
||||
+39
-27
@@ -21,38 +21,50 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIBezierPath {
|
||||
class SPAppleMusicButton: SPButton {
|
||||
|
||||
func resizeTo(width: CGFloat) {
|
||||
let currentWidth = self.bounds.width
|
||||
let relativeFactor = width / currentWidth
|
||||
self.apply(CGAffineTransform(scaleX: relativeFactor, y: relativeFactor))
|
||||
var mode: Mode = .unselect {
|
||||
didSet {
|
||||
self.updateStyle(animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
func convertToImage(fill: Bool, stroke: Bool, color: UIColor = .black) -> UIImage {
|
||||
UIGraphicsBeginImageContextWithOptions(CGSize(width: self.bounds.width, height: self.bounds.height), false, 0.0)
|
||||
let context = UIGraphicsGetCurrentContext()
|
||||
context!.setStrokeColor(color.cgColor)
|
||||
context!.setFillColor(color.cgColor)
|
||||
if stroke {
|
||||
self.stroke()
|
||||
var selectColor: UIColor = UIColor.init(hex: "FD2D55") {
|
||||
didSet {
|
||||
self.updateStyle(animated: false)
|
||||
}
|
||||
if fill {
|
||||
self.fill()
|
||||
}
|
||||
|
||||
var baseColor: UIColor = UIColor.init(hex: "F8F7FC") {
|
||||
didSet {
|
||||
self.updateStyle(animated: false)
|
||||
}
|
||||
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return image!
|
||||
}
|
||||
|
||||
override func commonInit() {
|
||||
super.commonInit()
|
||||
self.layer.cornerRadius = 8
|
||||
self.titleLabel?.font = UIFont.system(type: .DemiBold, size: 15)
|
||||
self.contentEdgeInsets = UIEdgeInsets.init(top: 12, left: 27, bottom: 12, right: 27)
|
||||
self.mode = .unselect
|
||||
}
|
||||
|
||||
private func updateStyle(animated: Bool) {
|
||||
switch self.mode {
|
||||
case .select:
|
||||
self.backgroundColor = self.selectColor
|
||||
self.setTitleColor(UIColor.white)
|
||||
break
|
||||
case .unselect:
|
||||
self.backgroundColor = self.baseColor
|
||||
self.setTitleColor(self.selectColor)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
enum Mode {
|
||||
case select
|
||||
case unselect
|
||||
}
|
||||
}
|
||||
|
||||
public struct SPBezierPath {
|
||||
|
||||
public static func setContext() {
|
||||
UIGraphicsBeginImageContextWithOptions(CGSize(width: 1, height: 1), false, 0)
|
||||
}
|
||||
|
||||
public static func endContext() {
|
||||
UIGraphicsEndImageContext()
|
||||
}
|
||||
}
|
||||
+30
-23
@@ -21,35 +21,42 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIWindow {
|
||||
public class SPButton: UIButton {
|
||||
|
||||
static var key: UIWindow? {
|
||||
return UIApplication.shared.keyWindow
|
||||
var gradientView: SPGradientView? {
|
||||
didSet {
|
||||
self.gradientView?.isUserInteractionEnabled = false
|
||||
if self.gradientView?.superview == nil {
|
||||
if self.gradientView != nil {
|
||||
self.insertSubview(self.gradientView!, at: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static var topSafeArea: CGFloat {
|
||||
var topSafeArea: CGFloat = 0
|
||||
if let window = UIWindow.key {
|
||||
if #available(iOS 11.0, *) {
|
||||
topSafeArea = window.safeAreaInsets.top
|
||||
}
|
||||
} else {
|
||||
topSafeArea = 0
|
||||
var round: Bool = false {
|
||||
didSet {
|
||||
self.layoutSubviews()
|
||||
}
|
||||
|
||||
return topSafeArea
|
||||
}
|
||||
|
||||
static var bottomSafeArea: CGFloat {
|
||||
var bottomSafeArea: CGFloat = 0
|
||||
if let window = UIWindow.key {
|
||||
if #available(iOS 11.0, *) {
|
||||
bottomSafeArea = window.safeAreaInsets.bottom
|
||||
}
|
||||
} else {
|
||||
bottomSafeArea = 0
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
internal func commonInit() {}
|
||||
|
||||
override public func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.gradientView?.setEqualsBoundsFromSuperview()
|
||||
if self.round {
|
||||
self.round()
|
||||
}
|
||||
|
||||
return bottomSafeArea
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPDotButton: SPButton {
|
||||
|
||||
var customSideSize: CGFloat = 26 {
|
||||
didSet {
|
||||
self.sizeToFit()
|
||||
}
|
||||
}
|
||||
|
||||
var dotColor: UIColor = UIColor.white {
|
||||
didSet {
|
||||
for dotView in self.dotsView {
|
||||
dotView.backgroundColor = self.dotColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override var isHighlighted: Bool{
|
||||
didSet{
|
||||
if isHighlighted{
|
||||
UIView.animate(withDuration: 0.1, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 1.0, options: [.curveEaseOut, .beginFromCurrentState], animations: {
|
||||
for dotView in self.dotsView {
|
||||
dotView.alpha = 0.35
|
||||
}
|
||||
}, completion: nil)
|
||||
}else{
|
||||
UIView.animate(withDuration: 0.35, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 1.0, options: [.curveEaseOut, .beginFromCurrentState], animations: {
|
||||
for dotView in self.dotsView {
|
||||
dotView.alpha = 1
|
||||
}
|
||||
}, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var dotsView: [UIView] = []
|
||||
|
||||
override func commonInit() {
|
||||
super.commonInit()
|
||||
self.backgroundColor = UIColor.black.withAlphaComponent(0.2)
|
||||
for _ in 0...2 {
|
||||
let dotView = UIView()
|
||||
dotView.isUserInteractionEnabled = false
|
||||
dotView.backgroundColor = self.dotColor
|
||||
self.dotsView.append(dotView)
|
||||
self.addSubview(dotView)
|
||||
}
|
||||
}
|
||||
|
||||
override func sizeToFit() {
|
||||
super.sizeToFit()
|
||||
self.setWidth(self.customSideSize)
|
||||
self.setHeight(self.customSideSize)
|
||||
self.layoutSubviews()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let space: CGFloat = 2
|
||||
let sideSize: CGFloat = 4
|
||||
|
||||
let insest: CGFloat = (self.frame.width - (sideSize * 3) - (space * 2)) / 2
|
||||
|
||||
var currentXPosition: CGFloat = insest
|
||||
|
||||
for dotView in self.dotsView {
|
||||
dotView.setWidth(sideSize)
|
||||
dotView.setHeight(sideSize)
|
||||
dotView.setYCenteringFromSuperview()
|
||||
dotView.frame.origin.x = currentXPosition
|
||||
dotView.round()
|
||||
currentXPosition += (sideSize + space)
|
||||
}
|
||||
|
||||
self.round()
|
||||
}
|
||||
}
|
||||
+23
-16
@@ -21,31 +21,31 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPNativeOS11Button: SPDownloadingButton {
|
||||
class SPNativeLargeButton: SPDownloadingButton {
|
||||
|
||||
override var isHighlighted: Bool {
|
||||
didSet {
|
||||
if isHighlighted {
|
||||
self.backgroundColor = self.backgroundColor?.withAlphaComponent(0.7)
|
||||
if self.gradientView == nil {
|
||||
if isHighlighted {
|
||||
self.backgroundColor = self.backgroundColor?.withAlphaComponent(0.7)
|
||||
} else {
|
||||
self.backgroundColor = self.backgroundColor?.withAlphaComponent(1)
|
||||
}
|
||||
} else {
|
||||
self.backgroundColor = self.backgroundColor?.withAlphaComponent(1)
|
||||
self.backgroundColor = self.backgroundColor?.withAlphaComponent(0)
|
||||
if isHighlighted {
|
||||
self.gradientView?.alpha = 0.7
|
||||
} else {
|
||||
self.gradientView?.alpha = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
override func commonInit() {
|
||||
super.commonInit()
|
||||
self.titleLabel?.font = UIFont.system(type: UIFont.BoldType.DemiBold, size: 16)
|
||||
self.setTitleColorForNoramlAndHightlightedStates(color: UIColor.white)
|
||||
self.setTitleColor(UIColor.white)
|
||||
self.backgroundColor = SPNativeStyleKit.Colors.blue
|
||||
self.layer.masksToBounds = true
|
||||
self.layer.cornerRadius = 8
|
||||
@@ -61,5 +61,12 @@ class SPNativeOS11Button: SPDownloadingButton {
|
||||
self.setWidth(width)
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.gradientView?.setEqualsBoundsFromSuperview()
|
||||
self.gradientView?.layer.cornerRadius = self.layer.cornerRadius
|
||||
self.gradientView?.gradientLayer.cornerRadius = self.layer.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPSocialIconButton: UIButton {
|
||||
class SPSocialButton: UIButton {
|
||||
|
||||
let iconView = SPSocialIconView.init()
|
||||
var widthIconFactor: CGFloat = 0.5
|
||||
@@ -1,8 +1,29 @@
|
||||
// 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: Style = . small {
|
||||
var style: SPNavigationTitleStyle = . small {
|
||||
didSet {
|
||||
self.updateStyle()
|
||||
}
|
||||
@@ -47,7 +68,7 @@ public class SPFakeBarView: UIView {
|
||||
private let blurView = UIVisualEffectView.init(style: .extraLight)
|
||||
private let separatorView = UIView()
|
||||
|
||||
init(style: Style) {
|
||||
init(style: SPNavigationTitleStyle) {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.style = style
|
||||
self.commonInit()
|
||||
@@ -94,7 +115,7 @@ public class SPFakeBarView: UIView {
|
||||
|
||||
self.leftButton.setTitleColor(self.elementsColor)
|
||||
self.leftButton.titleLabel?.textAlignment = .left
|
||||
self.leftButton.titleLabel?.font = UIFont.system(type: .Regular, size: 17)
|
||||
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
|
||||
@@ -103,8 +124,8 @@ public class SPFakeBarView: UIView {
|
||||
|
||||
self.rightButton.setTitleColor(self.elementsColor)
|
||||
self.rightButton.titleLabel?.textAlignment = .right
|
||||
self.rightButton.titleLabel?.font = UIFont.system(type: .Regular, size: 17)
|
||||
self.rightButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 17)
|
||||
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
|
||||
@@ -177,10 +198,5 @@ public class SPFakeBarView: UIView {
|
||||
self.heightConstraint?.constant = self.height
|
||||
self.updateConstraints()
|
||||
}
|
||||
|
||||
enum Style {
|
||||
case small
|
||||
case large
|
||||
case stork
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
-13
@@ -21,28 +21,36 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPGradientButton: SPDownloadingButton {
|
||||
class SPImageView: UIImageView {
|
||||
|
||||
let gradientView = SPGradientView.init()
|
||||
var round: Bool = false {
|
||||
didSet {
|
||||
self.layoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.gradientView.setStartColorPosition(SPGradientView.Position.MediumLeft)
|
||||
self.gradientView.setEndColorPosition(SPGradientView.Position.MediumRight)
|
||||
self.gradientView.startColor = UIColor.init(hex: "5737F6")
|
||||
self.gradientView.endColor = UIColor.init(hex: "956BFE")
|
||||
self.gradientView.isUserInteractionEnabled = false
|
||||
self.layer.masksToBounds = true
|
||||
self.addSubview(self.gradientView)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
override init(image: UIImage?) {
|
||||
super.init(image: image)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
internal func commonInit() {}
|
||||
|
||||
override public func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.gradientView.setEqualsBoundsFromSuperview()
|
||||
if self.round {
|
||||
self.layer.masksToBounds = true
|
||||
self.round()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+9
-5
@@ -21,13 +21,17 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIScreen {
|
||||
public class SPLabel: UILabel {
|
||||
|
||||
var minSideSize: CGFloat {
|
||||
return min(UIScreen.main.bounds.width, UIScreen.main.bounds.height)
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
var widthLessThanHeight: Bool {
|
||||
return UIScreen.main.bounds.width < UIScreen.main.bounds.height
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
internal func commonInit() {}
|
||||
}
|
||||
+32
-24
@@ -21,35 +21,43 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPMengButton: SPGradientButton {
|
||||
class SPTextField: UITextField {
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
self.isFrameRounded = true
|
||||
|
||||
self.contentEdgeInsets = UIEdgeInsets.init(top: 12, left: 21, bottom: 12, right: 21)
|
||||
self.titleLabel?.font = UIFont.system(type: UIFont.BoldType.Bold, size: 17)
|
||||
self.setTitleColorForNoramlAndHightlightedStates(color: UIColor.white)
|
||||
self.layer.masksToBounds = true
|
||||
self.gradientView.layer.masksToBounds = true
|
||||
|
||||
self.gradientView.setStartColorPosition(SPGradientView.Position.MediumLeft)
|
||||
self.gradientView.setEndColorPosition(SPGradientView.Position.MediumRight)
|
||||
self.gradientView.startColor = UIColor.init(hex: "5737F6")
|
||||
self.gradientView.endColor = UIColor.init(hex: "956BFE")
|
||||
public var textInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) {
|
||||
didSet { setNeedsDisplay() }
|
||||
}
|
||||
|
||||
var cursorColor: UIColor = UIColor.blue {
|
||||
didSet {
|
||||
self.tintColor = self.cursorColor
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
if self.isFrameRounded {
|
||||
self.gradientView.round()
|
||||
self.round()
|
||||
}
|
||||
internal func commonInit() {}
|
||||
|
||||
open override func textRect(forBounds bounds: CGRect) -> CGRect {
|
||||
return bounds.inset(by: textInsets)
|
||||
}
|
||||
|
||||
open override func editingRect(forBounds bounds: CGRect) -> CGRect {
|
||||
return bounds.inset(by: textInsets)
|
||||
}
|
||||
|
||||
open override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
|
||||
return bounds.inset(by: textInsets)
|
||||
}
|
||||
|
||||
open override func drawText(in rect: CGRect) {
|
||||
super.drawText(in: rect.inset(by: textInsets))
|
||||
}
|
||||
}
|
||||
+22
-5
@@ -21,13 +21,30 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UIDevice {
|
||||
public class SPView: UIView {
|
||||
|
||||
public var isIphone: Bool {
|
||||
return UIDevice.current.userInterfaceIdiom == .phone
|
||||
var round: Bool = false {
|
||||
didSet {
|
||||
self.layoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
public var isIpad: Bool {
|
||||
return UIDevice.current.userInterfaceIdiom == .pad
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-2
@@ -21,7 +21,23 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public struct SPStyleKit {
|
||||
public class SPCollectionContainerCell<ContentView: UIView>: UICollectionViewCell {
|
||||
|
||||
private init() {}
|
||||
let view = ContentView.init()
|
||||
var currentIndexPath: IndexPath?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.addSubview(view)
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.view.setEqualsFrameFromBounds(self)
|
||||
}
|
||||
}
|
||||
+12
-21
@@ -25,29 +25,20 @@ public class SPCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
var currentIndexPath: IndexPath?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
internal func commonInit() {}
|
||||
|
||||
public override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
self.currentIndexPath = nil
|
||||
}
|
||||
}
|
||||
|
||||
public class SPCollectionContainerCell<ContentView: UIView>: UICollectionViewCell {
|
||||
|
||||
let view = ContentView.init()
|
||||
var currentIndexPath: IndexPath?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.addSubview(view)
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.view.setEqualsFrameFromBounds(self)
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -27,7 +27,7 @@ class SPImageCollectionViewCell: SPCollectionContainerCell<SPDownloadingImageVie
|
||||
super.init(frame: frame)
|
||||
self.view.layer.cornerRadius = 10
|
||||
self.view.contentMode = .scaleAspectFill
|
||||
self.view.setNativeStyle()
|
||||
self.view.setNative()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
@@ -37,6 +37,6 @@ class SPImageCollectionViewCell: SPCollectionContainerCell<SPDownloadingImageVie
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
self.view.contentMode = .scaleAspectFill
|
||||
self.view.setLoadingMode()
|
||||
self.view.startLoading()
|
||||
}
|
||||
}
|
||||
+1
-12
@@ -44,18 +44,7 @@ class SPMengTransformCollectionViewCell: SPCollectionViewCell {
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
|
||||
override func commonInit() {
|
||||
shadowContainerView.backgroundColor = UIColor.white
|
||||
shadowContainerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.addSubview(shadowContainerView)
|
||||
+1
-1
@@ -41,7 +41,7 @@ public class SPCollectionView: UICollectionView {
|
||||
commonInit()
|
||||
}
|
||||
|
||||
fileprivate func commonInit() {
|
||||
internal func commonInit() {
|
||||
self.layout.scrollDirection = .vertical
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.collectionViewLayout = self.layout
|
||||
+2
-12
@@ -46,17 +46,7 @@ class SPMengTransformCollectionView: SPCollectionView {
|
||||
var withParalax: Bool = true
|
||||
static var recomendedHeight: CGFloat = 310
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
override func commonInit() {
|
||||
self.layout.scrollDirection = .horizontal
|
||||
|
||||
self.layout.cellSideRatio = 1.23
|
||||
@@ -186,7 +176,7 @@ extension SPMengTransformCollectionView: UICollectionViewDelegate {
|
||||
let indexPath = self.indexPath(for: cell)!
|
||||
|
||||
let attributes = self.layoutAttributesForItem(at: indexPath)!
|
||||
if let rootController = SPRootViewController.controller {
|
||||
if let rootController = SPApp.rootController {
|
||||
let cellFrame = self.convert(attributes.frame, to: rootController.view)
|
||||
|
||||
if self.withParalax {
|
||||
+2
-2
@@ -291,8 +291,8 @@ class SPBaseContentTableViewCell: SPTableViewCell {
|
||||
|
||||
self.accessoryType = .none
|
||||
self.separatorInsetStyle = .beforeImage
|
||||
self.iconImageView.setLoadingMode()
|
||||
self.iconImageView.setNativeStyle()
|
||||
self.iconImageView.startLoading()
|
||||
self.iconImageView.setNative()
|
||||
|
||||
self.titleLabel.text = "Title"
|
||||
self.titleLabel.font = UIFont.system(type: UIFont.BoldType.Medium, size: 17)
|
||||
+1
-1
@@ -45,7 +45,7 @@ class SPFormButtonTableViewCell: UITableViewCell {
|
||||
self.backgroundColor = UIColor.white
|
||||
self.button.setTitle("Button", for: .normal)
|
||||
self.button.backgroundColor = UIColor.clear
|
||||
self.button.setTitleColorForNoramlAndHightlightedStates(color: SPNativeStyleKit.Colors.blue)
|
||||
self.button.setTitleColor(SPNativeStyleKit.Colors.blue)
|
||||
self.button.titleLabel?.font = UIFont.system(type: .Medium, size: 17)
|
||||
self.selectionStyle = .none
|
||||
self.contentView.addSubview(self.button)
|
||||
+1
-1
@@ -82,7 +82,7 @@ class SPFormFeaturedTitleTableViewCell: UITableViewCell {
|
||||
|
||||
self.withButton = false
|
||||
self.button.setTitle("Button Title", for: UIControl.State.normal)
|
||||
self.button.setTitleColorForNoramlAndHightlightedStates(color: SPNativeStyleKit.Colors.blue)
|
||||
self.button.setTitleColor(SPNativeStyleKit.Colors.blue)
|
||||
self.button.titleLabel?.font = UIFont.system(type: .Medium, size: 17)
|
||||
self.button.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.button.titleLabel?.textAlignment = .right
|
||||
@@ -0,0 +1,62 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
public struct SPStorkController {
|
||||
|
||||
static func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if let controller = self.controller(for: scrollView) {
|
||||
if let presentationController = controller.presentationController as? SPStorkPresentationController {
|
||||
let translation = -(scrollView.contentOffset.y + scrollView.contentInset.top)
|
||||
if translation >= 0 {
|
||||
scrollView.subviews.forEach {
|
||||
$0.transform = CGAffineTransform(translationX: 0, y: -translation)
|
||||
}
|
||||
if presentationController.pan?.state != UIGestureRecognizer.State.changed {
|
||||
presentationController.scrollViewDidScroll(translation)
|
||||
}
|
||||
} else {
|
||||
presentationController.scrollViewDidScroll(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private func controller(for view: UIView) -> UIViewController? {
|
||||
var nextResponder = view.next
|
||||
while nextResponder != nil && !(nextResponder! is UIViewController) {
|
||||
nextResponder = nextResponder!.next
|
||||
}
|
||||
return nextResponder as? UIViewController
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
|
||||
var isPresentedAsStork: Bool {
|
||||
return transitioningDelegate is SPStorkTransitioningDelegate
|
||||
&& modalPresentationStyle == .custom
|
||||
&& presentingViewController != nil
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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,
|
||||
+15
-11
@@ -27,16 +27,16 @@ class SPStorkIndicatorView: UIView {
|
||||
didSet {
|
||||
switch self.style {
|
||||
case .line:
|
||||
SPAnimationSpring.animate(0.5, animations: {
|
||||
self.animate {
|
||||
self.leftView.transform = .identity
|
||||
self.rightView.transform = .identity
|
||||
}, options: .curveEaseOut)
|
||||
}
|
||||
case .arrow:
|
||||
SPAnimationSpring.animate(0.5, animations: {
|
||||
self.animate {
|
||||
let angle = CGFloat(20 * Float.pi / 180)
|
||||
self.leftView.transform = CGAffineTransform.init(rotationAngle: angle)
|
||||
self.rightView.transform = CGAffineTransform.init(rotationAngle: -angle)
|
||||
}, options: .curveEaseOut)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
+397
@@ -0,0 +1,397 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPStorkPresentationController: UIPresentationController, UIGestureRecognizerDelegate {
|
||||
|
||||
var isSwipeToDismissEnabled: Bool = true
|
||||
var showIndicator: Bool = true
|
||||
var transitioningDelegate: SPStorkTransitioningDelegate?
|
||||
var pan: UIPanGestureRecognizer?
|
||||
|
||||
private var indicatorView = SPStorkIndicatorView()
|
||||
private var gradeView: UIView = UIView()
|
||||
private let snapshotViewContainer = UIView()
|
||||
private var snapshotView: UIView?
|
||||
private let backgroundView = UIView()
|
||||
|
||||
private var snapshotViewTopConstraint: NSLayoutConstraint?
|
||||
private var snapshotViewWidthConstraint: NSLayoutConstraint?
|
||||
private var snapshotViewAspectRatioConstraint: NSLayoutConstraint?
|
||||
private var workGester: Bool = false
|
||||
private var startDismissing: Bool = false
|
||||
|
||||
private var topSpace: CGFloat {
|
||||
let statusBarHeight: CGFloat = UIApplication.shared.statusBarFrame.height
|
||||
return (statusBarHeight < 25) ? 30 : statusBarHeight
|
||||
}
|
||||
|
||||
private var alpha: CGFloat {
|
||||
return 0.51
|
||||
}
|
||||
|
||||
private var cornerRadius: CGFloat {
|
||||
return 10
|
||||
}
|
||||
|
||||
private var scaleForPresentingView: CGFloat {
|
||||
guard let containerView = containerView else { return 0 }
|
||||
let factor = 1 - (topSpace * 2 / containerView.frame.height)
|
||||
return factor
|
||||
}
|
||||
|
||||
override var frameOfPresentedViewInContainerView: CGRect {
|
||||
guard let containerView = containerView else { return .zero }
|
||||
let yOffset: CGFloat = topSpace + 13
|
||||
return CGRect(x: 0, y: yOffset, width: containerView.bounds.width, height: containerView.bounds.height - yOffset)
|
||||
}
|
||||
|
||||
override func presentationTransitionWillBegin() {
|
||||
super.presentationTransitionWillBegin()
|
||||
|
||||
guard let containerView = self.containerView, let presentedView = self.presentedView, let window = containerView.window else { return }
|
||||
|
||||
if self.showIndicator {
|
||||
presentedView.addSubview(self.indicatorView)
|
||||
}
|
||||
self.updateLayoutIndicator()
|
||||
self.indicatorView.style = .arrow
|
||||
self.gradeView.alpha = 0
|
||||
|
||||
let initialFrame: CGRect = presentingViewController.isPresentedAsStork ? presentingViewController.view.frame : containerView.bounds
|
||||
|
||||
containerView.insertSubview(self.snapshotViewContainer, belowSubview: presentedViewController.view)
|
||||
self.snapshotViewContainer.frame = initialFrame
|
||||
self.updateSnapshot()
|
||||
self.backgroundView.backgroundColor = UIColor.black
|
||||
self.backgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||
containerView.insertSubview(self.backgroundView, belowSubview: self.snapshotViewContainer)
|
||||
NSLayoutConstraint.activate([
|
||||
self.backgroundView.topAnchor.constraint(equalTo: window.topAnchor),
|
||||
self.backgroundView.leftAnchor.constraint(equalTo: window.leftAnchor),
|
||||
self.backgroundView.rightAnchor.constraint(equalTo: window.rightAnchor),
|
||||
self.backgroundView.bottomAnchor.constraint(equalTo: window.bottomAnchor)
|
||||
])
|
||||
|
||||
let transformForSnapshotView = CGAffineTransform.identity
|
||||
.translatedBy(x: 0, y: -snapshotViewContainer.frame.origin.y)
|
||||
.translatedBy(x: 0, y: self.topSpace)
|
||||
.translatedBy(x: 0, y: -snapshotViewContainer.frame.height / 2)
|
||||
.scaledBy(x: scaleForPresentingView, y: scaleForPresentingView)
|
||||
.translatedBy(x: 0, y: snapshotViewContainer.frame.height / 2)
|
||||
|
||||
self.addCornerRadiusAnimation(for: self.snapshotView, cornerRadius: self.cornerRadius, duration: 0.6)
|
||||
self.snapshotView?.layer.masksToBounds = true
|
||||
if #available(iOS 11.0, *) {
|
||||
presentedView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
}
|
||||
presentedView.layer.cornerRadius = self.cornerRadius
|
||||
presentedView.layer.masksToBounds = true
|
||||
|
||||
var rootSnapshotView: UIView?
|
||||
var rootSnapshotRoundedView: UIView?
|
||||
|
||||
if presentingViewController.isPresentedAsStork {
|
||||
guard let rootController = presentingViewController.presentingViewController, let snapshotView = rootController.view.snapshotView(afterScreenUpdates: false) else { return }
|
||||
|
||||
containerView.insertSubview(snapshotView, aboveSubview: self.backgroundView)
|
||||
snapshotView.frame = initialFrame
|
||||
snapshotView.transform = transformForSnapshotView
|
||||
snapshotView.alpha = self.alpha
|
||||
snapshotView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotView.layer.masksToBounds = true
|
||||
rootSnapshotView = snapshotView
|
||||
|
||||
let snapshotRoundedView = UIView()
|
||||
snapshotRoundedView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotRoundedView.layer.masksToBounds = true
|
||||
containerView.insertSubview(snapshotRoundedView, aboveSubview: snapshotView)
|
||||
snapshotRoundedView.frame = initialFrame
|
||||
snapshotRoundedView.transform = transformForSnapshotView
|
||||
rootSnapshotRoundedView = snapshotRoundedView
|
||||
}
|
||||
|
||||
presentedViewController.transitionCoordinator?.animate(
|
||||
alongsideTransition: { [weak self] context in
|
||||
guard let `self` = self else { return }
|
||||
self.snapshotView?.transform = transformForSnapshotView
|
||||
self.gradeView.alpha = self.alpha
|
||||
}, completion: { _ in
|
||||
self.snapshotView?.transform = .identity
|
||||
rootSnapshotView?.removeFromSuperview()
|
||||
rootSnapshotRoundedView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
override func presentationTransitionDidEnd(_ completed: Bool) {
|
||||
super.presentationTransitionDidEnd(completed)
|
||||
guard let containerView = containerView else { return }
|
||||
self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView
|
||||
self.snapshotViewContainer.transform = .identity
|
||||
self.snapshotViewContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.snapshotViewContainer.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
|
||||
self.updateSnapshotAspectRatio()
|
||||
|
||||
if self.isSwipeToDismissEnabled {
|
||||
self.pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
|
||||
self.pan!.delegate = self
|
||||
self.pan!.maximumNumberOfTouches = 1
|
||||
self.pan!.cancelsTouchesInView = false
|
||||
self.presentedViewController.view.addGestureRecognizer(self.pan!)
|
||||
}
|
||||
}
|
||||
|
||||
override func dismissalTransitionWillBegin() {
|
||||
super.dismissalTransitionWillBegin()
|
||||
guard let containerView = containerView else { return }
|
||||
self.startDismissing = true
|
||||
|
||||
let initialFrame: CGRect = presentingViewController.isPresentedAsStork ? presentingViewController.view.frame : containerView.bounds
|
||||
|
||||
let initialTransform = CGAffineTransform.identity
|
||||
.translatedBy(x: 0, y: -initialFrame.origin.y)
|
||||
.translatedBy(x: 0, y: self.topSpace)
|
||||
.translatedBy(x: 0, y: -initialFrame.height / 2)
|
||||
.scaledBy(x: scaleForPresentingView, y: scaleForPresentingView)
|
||||
.translatedBy(x: 0, y: initialFrame.height / 2)
|
||||
|
||||
self.snapshotViewTopConstraint?.isActive = false
|
||||
self.snapshotViewWidthConstraint?.isActive = false
|
||||
self.snapshotViewAspectRatioConstraint?.isActive = false
|
||||
self.snapshotViewContainer.translatesAutoresizingMaskIntoConstraints = true
|
||||
self.snapshotViewContainer.frame = initialFrame
|
||||
self.snapshotViewContainer.transform = initialTransform
|
||||
|
||||
let finalCornerRadius = presentingViewController.isPresentedAsStork ? self.cornerRadius : 0
|
||||
let finalTransform: CGAffineTransform = .identity
|
||||
|
||||
self.addCornerRadiusAnimation(for: self.snapshotView, cornerRadius: finalCornerRadius, duration: 0.6)
|
||||
|
||||
var rootSnapshotView: UIView?
|
||||
var rootSnapshotRoundedView: UIView?
|
||||
|
||||
if presentingViewController.isPresentedAsStork {
|
||||
guard let rootController = presentingViewController.presentingViewController, let snapshotView = rootController.view.snapshotView(afterScreenUpdates: false) else { return }
|
||||
|
||||
containerView.insertSubview(snapshotView, aboveSubview: backgroundView)
|
||||
snapshotView.frame = initialFrame
|
||||
snapshotView.transform = initialTransform
|
||||
rootSnapshotView = snapshotView
|
||||
snapshotView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotView.layer.masksToBounds = true
|
||||
|
||||
let snapshotRoundedView = UIView()
|
||||
snapshotRoundedView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotRoundedView.layer.masksToBounds = true
|
||||
snapshotRoundedView.backgroundColor = UIColor.black.withAlphaComponent(1 - self.alpha)
|
||||
containerView.insertSubview(snapshotRoundedView, aboveSubview: snapshotView)
|
||||
snapshotRoundedView.frame = initialFrame
|
||||
snapshotRoundedView.transform = initialTransform
|
||||
rootSnapshotRoundedView = snapshotRoundedView
|
||||
}
|
||||
|
||||
presentedViewController.transitionCoordinator?.animate(
|
||||
alongsideTransition: { [weak self] context in
|
||||
guard let `self` = self else { return }
|
||||
self.snapshotView?.transform = .identity
|
||||
self.snapshotViewContainer.transform = finalTransform
|
||||
self.gradeView.alpha = 0
|
||||
}, completion: { _ in
|
||||
rootSnapshotView?.removeFromSuperview()
|
||||
rootSnapshotRoundedView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
override func dismissalTransitionDidEnd(_ completed: Bool) {
|
||||
super.dismissalTransitionDidEnd(completed)
|
||||
guard let containerView = containerView else { return }
|
||||
|
||||
self.backgroundView.removeFromSuperview()
|
||||
self.snapshotView?.removeFromSuperview()
|
||||
self.snapshotViewContainer.removeFromSuperview()
|
||||
|
||||
let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
presentedViewController.view.frame = offscreenFrame
|
||||
presentedViewController.view.transform = .identity
|
||||
}
|
||||
}
|
||||
|
||||
extension SPStorkPresentationController {
|
||||
|
||||
@objc func handlePan(gestureRecognizer: UIPanGestureRecognizer) {
|
||||
guard gestureRecognizer.isEqual(pan), self.isSwipeToDismissEnabled else { return }
|
||||
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
self.workGester = true
|
||||
self.indicatorView.style = .line
|
||||
self.presentingViewController.view.layer.removeAllAnimations()
|
||||
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: containerView)
|
||||
case .changed:
|
||||
self.workGester = true
|
||||
if self.isSwipeToDismissEnabled {
|
||||
let translation = gestureRecognizer.translation(in: presentedView)
|
||||
self.updatePresentedViewForTranslation(inVerticalDirection: translation.y)
|
||||
} else {
|
||||
gestureRecognizer.setTranslation(.zero, in: presentedView)
|
||||
}
|
||||
case .ended:
|
||||
self.workGester = false
|
||||
let translation = gestureRecognizer.translation(in: presentedView).y
|
||||
if translation >= 240 {
|
||||
presentedViewController.dismiss(animated: true, completion: nil)
|
||||
} else {
|
||||
self.indicatorView.style = .arrow
|
||||
UIView.animate(
|
||||
withDuration: 0.6,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 1,
|
||||
initialSpringVelocity: 1,
|
||||
options: [.curveEaseOut, .allowUserInteraction],
|
||||
animations: {
|
||||
self.snapshotView?.transform = .identity
|
||||
self.presentedView?.transform = .identity
|
||||
})
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ translation: CGFloat) {
|
||||
if !self.workGester {
|
||||
self.updatePresentedViewForTranslation(inVerticalDirection: translation)
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePresentedViewForTranslation(inVerticalDirection translation: CGFloat) {
|
||||
if self.startDismissing { return }
|
||||
|
||||
let elasticThreshold: CGFloat = 120
|
||||
let translationFactor: CGFloat = 1 / 2
|
||||
|
||||
if translation >= 0 {
|
||||
let translationForModal: CGFloat = {
|
||||
if translation >= elasticThreshold {
|
||||
let frictionLength = translation - elasticThreshold
|
||||
let frictionTranslation = 30 * atan(frictionLength / 120) + frictionLength / 10
|
||||
return frictionTranslation + (elasticThreshold * translationFactor)
|
||||
} else {
|
||||
return translation * translationFactor
|
||||
}
|
||||
}()
|
||||
|
||||
self.presentedView?.transform = CGAffineTransform(translationX: 0, y: translationForModal)
|
||||
|
||||
if !self.presentingViewController.isPresentedAsStork {
|
||||
let factor = 1 + (translationForModal / 6000)
|
||||
self.snapshotView?.transform = CGAffineTransform.init(scaleX: factor, y: factor)
|
||||
self.gradeView.alpha = self.alpha - ((factor - 1) * 15)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SPStorkPresentationController {
|
||||
|
||||
override func containerViewWillLayoutSubviews() {
|
||||
super.containerViewWillLayoutSubviews()
|
||||
guard let containerView = containerView else { return }
|
||||
self.updateSnapshotAspectRatio()
|
||||
if presentedViewController.view.isDescendant(of: containerView) {
|
||||
UIView.animate(withDuration: 0.1) { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
coordinator.animate(alongsideTransition: { contex in
|
||||
self.updateLayoutIndicator()
|
||||
}, completion: { [weak self] _ in
|
||||
self?.updateSnapshotAspectRatio()
|
||||
self?.updateSnapshot()
|
||||
})
|
||||
}
|
||||
|
||||
private func updateLayoutIndicator() {
|
||||
guard let presentedView = self.presentedView else { return }
|
||||
self.indicatorView.style = .line
|
||||
self.indicatorView.sizeToFit()
|
||||
self.indicatorView.frame.origin.y = 12
|
||||
self.indicatorView.center.x = presentedView.frame.width / 2
|
||||
}
|
||||
|
||||
private func updateSnapshot() {
|
||||
guard let currentSnapshotView = presentingViewController.view.snapshotView(afterScreenUpdates: true) else { return }
|
||||
self.snapshotView?.removeFromSuperview()
|
||||
self.snapshotViewContainer.addSubview(currentSnapshotView)
|
||||
self.constraints(view: currentSnapshotView, to: self.snapshotViewContainer)
|
||||
self.snapshotView = currentSnapshotView
|
||||
self.gradeView.removeFromSuperview()
|
||||
self.gradeView.backgroundColor = UIColor.black
|
||||
self.snapshotView!.addSubview(self.gradeView)
|
||||
self.constraints(view: self.gradeView, to: self.snapshotView!)
|
||||
}
|
||||
|
||||
private func updateSnapshotAspectRatio() {
|
||||
guard let containerView = containerView, snapshotViewContainer.translatesAutoresizingMaskIntoConstraints == false else { return }
|
||||
|
||||
self.snapshotViewTopConstraint?.isActive = false
|
||||
self.snapshotViewWidthConstraint?.isActive = false
|
||||
self.snapshotViewAspectRatioConstraint?.isActive = false
|
||||
|
||||
let snapshotReferenceSize = presentingViewController.view.frame.size
|
||||
let aspectRatio = snapshotReferenceSize.width / snapshotReferenceSize.height
|
||||
|
||||
self.snapshotViewTopConstraint = snapshotViewContainer.topAnchor.constraint(equalTo: containerView.topAnchor, constant: self.topSpace)
|
||||
self.snapshotViewWidthConstraint = snapshotViewContainer.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: scaleForPresentingView)
|
||||
self.snapshotViewAspectRatioConstraint = snapshotViewContainer.widthAnchor.constraint(equalTo: snapshotViewContainer.heightAnchor, multiplier: aspectRatio)
|
||||
|
||||
self.snapshotViewTopConstraint?.isActive = true
|
||||
self.snapshotViewWidthConstraint?.isActive = true
|
||||
self.snapshotViewAspectRatioConstraint?.isActive = true
|
||||
}
|
||||
|
||||
private func constraints(view: UIView, to superView: UIView) {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
view.topAnchor.constraint(equalTo: superView.topAnchor),
|
||||
view.leftAnchor.constraint(equalTo: superView.leftAnchor),
|
||||
view.rightAnchor.constraint(equalTo: superView.rightAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: superView.bottomAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
private func addCornerRadiusAnimation(for view: UIView?, cornerRadius: CGFloat, duration: CFTimeInterval) {
|
||||
guard let view = view else { return }
|
||||
let animation = CABasicAnimation(keyPath:"cornerRadius")
|
||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
|
||||
animation.fromValue = view.layer.cornerRadius
|
||||
animation.toValue = cornerRadius
|
||||
animation.duration = duration
|
||||
view.layer.add(animation, forKey: "cornerRadius")
|
||||
view.layer.cornerRadius = cornerRadius
|
||||
}
|
||||
}
|
||||
+2
@@ -24,9 +24,11 @@ import UIKit
|
||||
final class SPStorkPresentingAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
|
||||
guard let presentedViewController = transitionContext.viewController(forKey: .to) else {
|
||||
return
|
||||
}
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
containerView.addSubview(presentedViewController.view)
|
||||
presentedViewController.view.frame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
+3
@@ -23,6 +23,9 @@ import UIKit
|
||||
|
||||
public final class SPStorkTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
|
||||
|
||||
var isSwipeToDismissEnabled: Bool = true
|
||||
var showIndicator: Bool = true
|
||||
|
||||
public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
|
||||
let controller = SPStorkPresentationController(presentedViewController: presented, presenting: presenting)
|
||||
controller.transitioningDelegate = self
|
||||
@@ -31,5 +31,30 @@ struct SPApp {
|
||||
return Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
|
||||
}
|
||||
|
||||
static var rootController: UIViewController? {
|
||||
return UIApplication.shared.keyWindow?.rootViewController
|
||||
}
|
||||
|
||||
static func set(rootController: UIViewController, animatable: Bool = true) {
|
||||
|
||||
rootController.view.frame = UIScreen.main.bounds
|
||||
|
||||
let replaceRootViewController = {
|
||||
UIApplication.shared.keyWindow?.rootViewController = rootController
|
||||
}
|
||||
|
||||
if animatable {
|
||||
UIView.transition(
|
||||
with: UIApplication.shared.keyWindow ?? UIWindow(),
|
||||
duration: 0.5,
|
||||
options: UIView.AnimationOptions.transitionCrossDissolve,
|
||||
animations: {
|
||||
replaceRootViewController()
|
||||
}, completion: nil)
|
||||
} else {
|
||||
replaceRootViewController()
|
||||
}
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
public struct SPConstraintsAssistent {
|
||||
|
||||
static func setEqualSizeConstraint(_ subView: UIView, superVuew: UIView) {
|
||||
subView.translatesAutoresizingMaskIntoConstraints = false;
|
||||
let topMarginConstraint = NSLayoutConstraint(
|
||||
item: subView,
|
||||
attribute: NSLayoutConstraint.Attribute.topMargin,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: superVuew,
|
||||
attribute: NSLayoutConstraint.Attribute.top,
|
||||
multiplier: 1,
|
||||
constant: 0)
|
||||
|
||||
let bottomMarginConstraint = NSLayoutConstraint(
|
||||
item: subView,
|
||||
attribute: NSLayoutConstraint.Attribute.bottomMargin,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: superVuew,
|
||||
attribute: NSLayoutConstraint.Attribute.bottom,
|
||||
multiplier: 1,
|
||||
constant: 0)
|
||||
|
||||
let leadingMarginConstraint = NSLayoutConstraint(
|
||||
item: subView,
|
||||
attribute: NSLayoutConstraint.Attribute.leadingMargin,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: superVuew,
|
||||
attribute: NSLayoutConstraint.Attribute.leading,
|
||||
multiplier: 1,
|
||||
constant: 0)
|
||||
|
||||
let trailingMarginConstraint = NSLayoutConstraint(
|
||||
item: subView,
|
||||
attribute: NSLayoutConstraint.Attribute.trailingMargin,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: superVuew,
|
||||
attribute: NSLayoutConstraint.Attribute.trailing,
|
||||
multiplier: 1,
|
||||
constant: 0)
|
||||
|
||||
superVuew.addConstraints([
|
||||
topMarginConstraint, bottomMarginConstraint, leadingMarginConstraint, trailingMarginConstraint
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,11 +24,11 @@ import UIKit
|
||||
struct SPDevice {
|
||||
|
||||
static var isIphone: Bool {
|
||||
return UIDevice.current.isIphone
|
||||
return UIDevice.current.userInterfaceIdiom == .phone
|
||||
}
|
||||
|
||||
static var isIpad: Bool {
|
||||
return UIDevice.current.isIpad
|
||||
return UIDevice.current.userInterfaceIdiom == .pad
|
||||
}
|
||||
|
||||
struct Orientation {
|
||||
|
||||
@@ -23,10 +23,8 @@ import Foundation
|
||||
|
||||
extension Array {
|
||||
|
||||
func takeElements(count: Int) -> Array {
|
||||
if (count < self.count) {
|
||||
return Array(self[0..<count])
|
||||
}
|
||||
func get(count: Int) -> Array {
|
||||
if (count < self.count) { return Array(self[0..<count]) }
|
||||
return Array(self)
|
||||
}
|
||||
}
|
||||
@@ -35,11 +33,8 @@ extension Array where Element: Equatable {
|
||||
|
||||
mutating func removeDuplicates() {
|
||||
var result = [Element]()
|
||||
|
||||
for value in self {
|
||||
if result.contains(value) == false {
|
||||
result.append(value)
|
||||
}
|
||||
if result.contains(value) == false { result.append(value) }
|
||||
}
|
||||
self = result
|
||||
}
|
||||
@@ -48,9 +43,7 @@ extension Array where Element: Equatable {
|
||||
extension Array where Element: Hashable {
|
||||
|
||||
func after(item: Element) -> Element? {
|
||||
if let index = self.index(of: item), index + 1 < self.count {
|
||||
return self[index + 1]
|
||||
}
|
||||
if let index = self.index(of: item), index + 1 < self.count { return self[index + 1] }
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
func + (left: CGPoint, right: CGPoint) -> CGPoint {
|
||||
return CGPoint(x: left.x + right.x, y: left.y + right.y)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
+6
-39
@@ -23,50 +23,17 @@ import UIKit
|
||||
|
||||
extension UITabBarController {
|
||||
|
||||
func addTabBarItem(titleName: String, imageName: String, viewController: UIViewController) {
|
||||
|
||||
let image = UIImage.init(named: imageName)
|
||||
|
||||
self.addTabBarItem(
|
||||
titleName: titleName,
|
||||
image: image ?? UIImage(),
|
||||
viewController: viewController
|
||||
)
|
||||
}
|
||||
|
||||
func addTabBarItem(titleName: String, image: UIImage, viewController: UIViewController) {
|
||||
func addTabBarItem(title: String, image: UIImage, selectedImage: UIImage? = nil, controller: UIViewController) {
|
||||
|
||||
let tabBarItem = UITabBarItem(
|
||||
title: titleName,
|
||||
title: title,
|
||||
image: image,
|
||||
selectedImage: image
|
||||
selectedImage: selectedImage ?? image
|
||||
)
|
||||
|
||||
viewController.tabBarItem = tabBarItem
|
||||
controller.tabBarItem = tabBarItem
|
||||
|
||||
if self.viewControllers == nil {
|
||||
self.viewControllers = [viewController]
|
||||
} else {
|
||||
self.viewControllers?.append(viewController)
|
||||
}
|
||||
if self.viewControllers == nil { self.viewControllers = [controller] }
|
||||
else { self.viewControllers?.append(controller) }
|
||||
}
|
||||
|
||||
@objc func addTabBarItem(titleName: String, image: UIImage, selectedImage: UIImage, viewController: UIViewController) {
|
||||
|
||||
let tabBarItem = UITabBarItem(
|
||||
title: titleName,
|
||||
image: image,
|
||||
selectedImage: selectedImage
|
||||
)
|
||||
|
||||
viewController.tabBarItem = tabBarItem
|
||||
|
||||
if self.viewControllers == nil {
|
||||
self.viewControllers = [viewController]
|
||||
} else {
|
||||
self.viewControllers?.append(viewController)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -36,50 +36,24 @@ extension UITableView {
|
||||
}
|
||||
|
||||
var lastSectionWithRows: Int? {
|
||||
|
||||
if self.numberOfSections == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if self.numberOfSections == 0 { return nil }
|
||||
var section = self.numberOfSections - 1
|
||||
|
||||
if section < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if section < 0 { return nil }
|
||||
while section >= 0 {
|
||||
|
||||
if self.numberOfRows(inSection: section) != 0 {
|
||||
return section
|
||||
}
|
||||
|
||||
if self.numberOfRows(inSection: section) != 0 { return section }
|
||||
section -= 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var firstSectionWithRows: Int? {
|
||||
|
||||
if self.numberOfSections == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if self.numberOfSections == 0 { return nil }
|
||||
var section = 0
|
||||
|
||||
if section > self.numberOfSections - 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if section > self.numberOfSections - 1 { return nil }
|
||||
while section <= (self.numberOfSections - 1) {
|
||||
|
||||
if self.numberOfRows(inSection: section) != 0 {
|
||||
return section
|
||||
}
|
||||
|
||||
if self.numberOfRows(inSection: section) != 0 { return section }
|
||||
section += 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
+11
-14
@@ -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) {
|
||||
@@ -136,28 +133,28 @@ extension UIViewController {
|
||||
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 {
|
||||
return UIViewController.statusBarHeight
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
+1
-1
@@ -213,7 +213,7 @@ public class SPPermissionDialogController: SPBaseViewController {
|
||||
self.bottomLabel.sizeToFit()
|
||||
self.bottomLabel.setWidth(bottomLabelWidth)
|
||||
self.bottomLabel.center.x = size.width / 2
|
||||
self.bottomLabel.frame.bottomYPosition = size.height - self.bottomSafeArea - 30
|
||||
self.bottomLabel.frame.bottomYPosition = size.height - self.safeArea.bottom - 30
|
||||
self.bottomLabel.setShadowOffsetForLetters(blurRadius: 3, widthOffset: 0, heightOffset: 0, opacity: 0.18)
|
||||
|
||||
let bottomLabelAlpha: CGFloat = SPDevice.Orientation.isPortrait ? 1 : 0
|
||||
|
||||
@@ -40,5 +40,9 @@ public struct SPNativeStyleKit {
|
||||
static let midGray = UIColor.init(hex: "C7C7CC")
|
||||
static let gray = UIColor.init(hex: "8E8E93")
|
||||
static let black = UIColor.init(hex: "000000")
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ public enum SPSeparatorInsetStyle {
|
||||
public enum SPNavigationTitleStyle {
|
||||
case large
|
||||
case small
|
||||
case stork
|
||||
}
|
||||
|
||||
public enum SPSystemApp {
|
||||
|
||||
+4
-14
@@ -26,7 +26,7 @@ class SPAppStoreActionButton: SPDownloadingButton {
|
||||
var style: Style = .base {
|
||||
didSet {
|
||||
|
||||
self.setTitleColorForNoramlAndHightlightedStates(color: self.baseColor)
|
||||
self.setTitleColor(self.baseColor)
|
||||
self.setTitle(self.titleLabel?.text, for: UIControl.State.normal)
|
||||
|
||||
switch self.style {
|
||||
@@ -38,14 +38,14 @@ class SPAppStoreActionButton: SPDownloadingButton {
|
||||
case .main:
|
||||
self.backgroundColor = self.baseColor
|
||||
self.layer.borderWidth = 0
|
||||
self.setTitleColorForNoramlAndHightlightedStates(color: UIColor.white)
|
||||
self.setTitleColor(UIColor.white)
|
||||
self.titleLabel?.font = UIFont.system(type: .Bold, size: 14)
|
||||
self.contentEdgeInsets = UIEdgeInsets.init(top: 6, left: 15, bottom: 6, right: 15)
|
||||
break
|
||||
case .buyInStore:
|
||||
self.backgroundColor = self.baseColor
|
||||
self.layer.borderWidth = 0
|
||||
self.setTitleColorForNoramlAndHightlightedStates(color: UIColor.white)
|
||||
self.setTitleColor(UIColor.white)
|
||||
self.titleLabel?.font = UIFont.system(type: .Bold, size: 14)
|
||||
self.contentEdgeInsets = UIEdgeInsets.init(top: 8, left: 15, bottom: 8, right: 15)
|
||||
break
|
||||
@@ -73,17 +73,7 @@ class SPAppStoreActionButton: SPDownloadingButton {
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
override func commonInit() {
|
||||
self.style = .base
|
||||
self.layer.masksToBounds = true
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPDownloadingButton: UIButton {
|
||||
class SPDownloadingButton: SPButton {
|
||||
|
||||
let activityIndicatorView = UIActivityIndicatorView.init()
|
||||
var isFrameRounded: Bool = false
|
||||
|
||||
@@ -71,7 +71,5 @@ class SPPlayCircleButton: UIButton {
|
||||
case pause
|
||||
case stop
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
public class SPRoundButton: SPRoundFrameButton {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
|
||||
private func commonInit() {
|
||||
self.backgroundColor = UIColor.white
|
||||
self.layer.borderWidth = 0
|
||||
}
|
||||
}
|
||||
|
||||
public class SPRoundLineButton: SPRoundFrameButton {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
|
||||
private func commonInit() {
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.layer.borderWidth = 2
|
||||
self.layer.borderColor = UIColor(hue: 0,
|
||||
saturation: 0,
|
||||
brightness: 100,
|
||||
alpha: 0.5).cgColor
|
||||
self.layer.borderColor = UIColor.init(white: 1, alpha: 0.5).cgColor
|
||||
}
|
||||
}
|
||||
|
||||
public class SPRoundFrameButton: UIButton {
|
||||
|
||||
override public func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
let minSide = min(self.frame.width, self.frame.height)
|
||||
self.layer.cornerRadius = minSide / 2
|
||||
self.clipsToBounds = true
|
||||
}
|
||||
}
|
||||
+12
-3
@@ -59,7 +59,7 @@ class SPBaseTableViewController: SPStatusBarManagerTableViewController {
|
||||
|
||||
func updateLayout(with size: CGSize) {
|
||||
let layoutIfShowKeyboard = {
|
||||
let height = size.height - (self.keyboardSize?.height ?? 0) - self.topSafeArea
|
||||
let height = size.height - (self.keyboardSize?.height ?? 0) - self.safeArea.top
|
||||
self.emptyProposeView?.frame = CGRect.init(
|
||||
x: 0, y: 0,
|
||||
width: size.width * self.emptyProposeViewWidthFactor,
|
||||
@@ -69,7 +69,7 @@ class SPBaseTableViewController: SPStatusBarManagerTableViewController {
|
||||
}
|
||||
|
||||
let layoutIfNotShowKeyboard = {
|
||||
let height = size.height - self.topSafeArea - self.bottomSafeArea
|
||||
let height = size.height - self.safeArea.top - self.safeArea.bottom
|
||||
self.emptyProposeView?.frame = CGRect.init(
|
||||
x: 0, y: 0,
|
||||
width: size.width * self.emptyProposeViewWidthFactor,
|
||||
@@ -93,7 +93,7 @@ class SPBaseTableViewController: SPStatusBarManagerTableViewController {
|
||||
} else {
|
||||
self.activityIndicatorView.center = CGPoint.init(
|
||||
x: size.width / 2,
|
||||
y: (size.height - self.topSafeArea - self.bottomSafeArea) / 2
|
||||
y: (size.height - self.safeArea.top - self.safeArea.bottom) / 2
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -299,4 +299,13 @@ class SPBaseTableViewController: SPStatusBarManagerTableViewController {
|
||||
@objc func dismiss(sender: Any) {
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
func addHideButton(title: String) {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(
|
||||
title: title,
|
||||
style: UIBarButtonItem.Style.done,
|
||||
target: self,
|
||||
action: #selector(self.dismiss(sender:))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+17
-4
@@ -59,14 +59,14 @@ public class SPBaseViewController: SPStatusBarManagerViewController {
|
||||
|
||||
func updateLayout(with size: CGSize) {
|
||||
|
||||
var contentHeight = size.height - self.topSafeArea
|
||||
var contentHeight = size.height - self.safeArea.top
|
||||
if self.isShowKeyboard {
|
||||
contentHeight = contentHeight - (self.keyboardSize?.height ?? 0)
|
||||
} else {
|
||||
contentHeight = contentHeight - self.bottomSafeArea
|
||||
contentHeight = contentHeight - self.safeArea.bottom
|
||||
}
|
||||
|
||||
let centerYPosition = self.topSafeArea + (contentHeight / 2)
|
||||
let centerYPosition = self.safeArea.top + (contentHeight / 2)
|
||||
|
||||
if self.activityIndicatorLayoutWithSafeArea {
|
||||
self.activityIndicatorView.center = CGPoint.init(
|
||||
@@ -88,7 +88,7 @@ public class SPBaseViewController: SPStatusBarManagerViewController {
|
||||
self.emptyProposeView?.frame = CGRect.init(
|
||||
x: 0, y: 0,
|
||||
width: emptyProposeViewWidth,
|
||||
height: (size.height - self.topSafeArea - self.bottomSafeArea) * self.emptyProposeViewHeightFactor
|
||||
height: (size.height - self.safeArea.top - self.safeArea.bottom) * self.emptyProposeViewHeightFactor
|
||||
)
|
||||
self.emptyProposeView?.center = CGPoint.init(
|
||||
x: size.width / 2,
|
||||
@@ -103,6 +103,19 @@ public class SPBaseViewController: SPStatusBarManagerViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func addHideButton(title: String) {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(
|
||||
title: title,
|
||||
style: UIBarButtonItem.Style.done,
|
||||
target: self,
|
||||
action: #selector(self.dismiss(sender:))
|
||||
)
|
||||
}
|
||||
|
||||
@objc func dismiss(sender: Any) {
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
//MARK: - keyboard
|
||||
var isShowKeyboard: Bool = false
|
||||
var keyboardSize: CGSize? = nil
|
||||
|
||||
+1
-1
@@ -166,7 +166,7 @@ class SPConfirmActionViewController: UIViewController {
|
||||
self.addSubview(self.label)
|
||||
|
||||
self.button.setTitle("Сancel", for: .normal)
|
||||
self.button.setTitleColorForNoramlAndHightlightedStates(color: SPNativeStyleKit.Colors.blue)
|
||||
self.button.setTitleColor(SPNativeStyleKit.Colors.blue)
|
||||
self.button.titleLabel?.font = UIFont.system(type: .DemiBold, size: 16)
|
||||
self.addSubview(self.button)
|
||||
}
|
||||
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPHiderViewController: SPBaseViewController {
|
||||
|
||||
let backgroundView = SPGradeBlurView.init()
|
||||
|
||||
private var durationAnimation: TimeInterval = 0.3
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.statusBar = .light
|
||||
|
||||
self.view.backgroundColor = UIColor.clear
|
||||
|
||||
self.backgroundView.setGradeColor(UIColor.black)
|
||||
self.activityIndicatorView.alpha = 0
|
||||
self.backgroundView.setGradeAlpha(0, blurRaius: 0)
|
||||
self.view.addSubview(self.backgroundView)
|
||||
|
||||
self.activityIndicatorView.startAnimating()
|
||||
self.view.addSubview(self.activityIndicatorView)
|
||||
|
||||
self.updateLayout(with: self.view.frame.size)
|
||||
}
|
||||
|
||||
override func updateLayout(with size: CGSize) {
|
||||
self.backgroundView.frame = CGRect.init(origin: CGPoint.zero, size: size)
|
||||
self.activityIndicatorView.center = CGPoint.init(x: size.width / 2, y: size.height / 2)
|
||||
}
|
||||
|
||||
func present(on viewController: UIViewController) {
|
||||
self.modalTransitionStyle = .crossDissolve
|
||||
self.modalPresentationStyle = .overCurrentContext
|
||||
viewController.present(self, animated: false) {
|
||||
SPAnimation.animate(self.durationAnimation, animations: {
|
||||
self.backgroundView.setGradeAlpha(0.5, blurRaius: 8)
|
||||
})
|
||||
SPAnimation.animate(self.durationAnimation * 1.5, animations: {
|
||||
self.activityIndicatorView.alpha = 1
|
||||
}, delay: self.durationAnimation / 1.5)
|
||||
}
|
||||
}
|
||||
|
||||
override func dismiss() {
|
||||
SPAnimation.animate(self.durationAnimation / 1.2, animations: {
|
||||
self.activityIndicatorView.alpha = 0
|
||||
})
|
||||
SPAnimation.animate(self.durationAnimation, animations: {
|
||||
self.backgroundView.setGradeAlpha(0, blurRaius: 0)
|
||||
}, delay: 0) {
|
||||
self.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
-13
@@ -183,19 +183,6 @@ extension SPNativeTableViewController {
|
||||
|
||||
}
|
||||
|
||||
//MARK: - hide button
|
||||
extension SPNativeTableViewController {
|
||||
|
||||
func addHideButton(title: String) {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(
|
||||
title: title,
|
||||
style: UIBarButtonItem.Style.done,
|
||||
target: self,
|
||||
action: #selector(self.dismiss(sender:))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - manage spaces
|
||||
extension SPNativeTableViewController {
|
||||
|
||||
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPProgressLoadingViewController: SPBaseViewController {
|
||||
|
||||
let titleLabel = UILabel.init()
|
||||
let subtitleLabel = UILabel.init()
|
||||
let progressView = UIProgressView.init(progressViewStyle: UIProgressView.Style.default)
|
||||
let commentLabel = UILabel.init()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.view.backgroundColor = SPNativeStyleKit.Colors.white
|
||||
|
||||
self.titleLabel.text = "Title"
|
||||
self.titleLabel.font = UIFont.system(type: UIFont.BoldType.Medium, size: 23)
|
||||
self.titleLabel.textColor = SPNativeStyleKit.Colors.black
|
||||
self.titleLabel.numberOfLines = 1
|
||||
self.view.addSubview(self.titleLabel)
|
||||
|
||||
self.subtitleLabel.text = "Subtitle..."
|
||||
self.subtitleLabel.setCenteringAlignment()
|
||||
self.subtitleLabel.font = UIFont.system(type: UIFont.BoldType.Regular, size: 13)
|
||||
self.subtitleLabel.textColor = SPNativeStyleKit.Colors.gray
|
||||
self.subtitleLabel.numberOfLines = 1
|
||||
self.view.addSubview(self.subtitleLabel)
|
||||
|
||||
self.progressView.progress = 0
|
||||
self.view.addSubview(self.progressView)
|
||||
|
||||
self.activityIndicatorView.color = SPNativeStyleKit.Colors.gray
|
||||
self.activityIndicatorView.startAnimating()
|
||||
self.view.addSubview(self.activityIndicatorView)
|
||||
|
||||
self.commentLabel.text = "Comment"
|
||||
self.commentLabel.setCenteringAlignment()
|
||||
self.commentLabel.font = UIFont.system(type: UIFont.BoldType.Regular, size: 11)
|
||||
self.commentLabel.textColor = SPNativeStyleKit.Colors.gray
|
||||
self.commentLabel.numberOfLines = 1
|
||||
self.view.addSubview(self.commentLabel)
|
||||
|
||||
self.updateLayout(with: self.view.frame.size)
|
||||
}
|
||||
|
||||
func set(progress: Float) {
|
||||
self.progressView.setProgress(progress, animated: true)
|
||||
}
|
||||
|
||||
override func updateLayout(with size: CGSize) {
|
||||
self.titleLabel.sizeToFit()
|
||||
self.titleLabel.center.x = size.width / 2
|
||||
|
||||
self.subtitleLabel.sizeToFit()
|
||||
self.subtitleLabel.center.x = size.width / 2
|
||||
|
||||
self.progressView.setWidth(size.width * 0.55)
|
||||
self.progressView.center.x = size.width / 2
|
||||
|
||||
self.activityIndicatorView.sizeToFit()
|
||||
self.activityIndicatorView.center.x = size.width / 2
|
||||
|
||||
let allHeight = self.titleLabel.frame.height + 5 + self.subtitleLabel.frame.height + 30 + self.progressView.frame.height + 30 + self.activityIndicatorView.frame.height
|
||||
|
||||
self.titleLabel.frame.origin.y = (size.height - allHeight - self.topSafeArea) / 2
|
||||
self.subtitleLabel.frame.origin.y = self.titleLabel.frame.bottomYPosition + 5
|
||||
self.progressView.frame.origin.y = self.subtitleLabel.frame.bottomYPosition + 30
|
||||
self.activityIndicatorView.frame.origin.y = self.progressView.frame.bottomYPosition + 30
|
||||
|
||||
self.commentLabel.sizeToFit()
|
||||
self.commentLabel.center.x = size.width / 2
|
||||
self.commentLabel.frame.origin.y = size.height - (self.view.bottomSafeArea + self.commentLabel.frame.height + 5)
|
||||
}
|
||||
}
|
||||
+18
-5
@@ -83,7 +83,7 @@ class SPProposeViewController: SPBaseViewController {
|
||||
self.areaView.layoutSubviews()
|
||||
self.areaView.sizeToFit()
|
||||
self.areaView.frame.origin.x = self.space
|
||||
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (UIWindow.bottomSafeArea / 2) - self.space
|
||||
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (self.bottomSafeArea / 2) - self.space
|
||||
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class SPProposeViewController: SPBaseViewController {
|
||||
SPAnimationSpring.animate(self.animationDuration, animations: {
|
||||
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
|
||||
|
||||
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (UIWindow.bottomSafeArea / 2) - self.space
|
||||
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (self.bottomSafeArea / 2) - self.space
|
||||
}, spring: 1,
|
||||
velocity: 1,
|
||||
options: .transitionCurlUp)
|
||||
@@ -134,7 +134,7 @@ class SPProposeViewController: SPBaseViewController {
|
||||
|
||||
let returnAreaViewToPoint = {
|
||||
SPAnimationSpring.animate(self.animationDuration, animations: {
|
||||
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (UIWindow.bottomSafeArea / 2) - self.space
|
||||
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (self.bottomSafeArea / 2) - self.space
|
||||
}, spring: 1,
|
||||
velocity: 1,
|
||||
options: .transitionCurlDown,
|
||||
@@ -173,7 +173,7 @@ class SPProposeViewController: SPBaseViewController {
|
||||
let titleLabel = UILabel()
|
||||
let subtitleLabel = UILabel()
|
||||
let imageView = SPDownloadingImageView()
|
||||
let button = SPNativeOS11Button()
|
||||
let button = SPNativeLargeButton()
|
||||
let closeButton = SPSystemIconButton(type: SPSystemIconType.close)
|
||||
|
||||
var imageSideSize: CGFloat = 160
|
||||
@@ -206,7 +206,7 @@ class SPProposeViewController: SPBaseViewController {
|
||||
self.addSubview(self.imageView)
|
||||
|
||||
self.button.titleLabel?.font = UIFont.system(type: UIFont.BoldType.Medium, size: 15)
|
||||
self.button.setTitleColorForNoramlAndHightlightedStates(color: SPNativeStyleKit.Colors.black)
|
||||
self.button.setTitleColor(SPNativeStyleKit.Colors.black)
|
||||
self.button.backgroundColor = UIColor.init(hex: "D4D3DB")
|
||||
self.addSubview(self.button)
|
||||
|
||||
@@ -266,4 +266,17 @@ class SPProposeViewController: SPBaseViewController {
|
||||
var image: UIImage?
|
||||
var complection: (_ isConfirmed: Bool)->()
|
||||
}
|
||||
|
||||
var bottomSafeArea: CGFloat {
|
||||
var bottomSafeArea: CGFloat = 0
|
||||
if let window = UIApplication.shared.keyWindow {
|
||||
if #available(iOS 11.0, *) {
|
||||
bottomSafeArea = window.safeAreaInsets.bottom
|
||||
}
|
||||
} else {
|
||||
bottomSafeArea = 0
|
||||
}
|
||||
|
||||
return bottomSafeArea
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import UIKit
|
||||
|
||||
struct SPRootViewController {
|
||||
|
||||
static var controller: UIViewController? {
|
||||
return UIApplication.shared.keyWindow?.rootViewController
|
||||
}
|
||||
|
||||
static func set(_ rootController: UIViewController, animatable: Bool = true) {
|
||||
|
||||
rootController.view.frame = UIScreen.main.bounds
|
||||
|
||||
let replaceRootViewController = {
|
||||
UIApplication.shared.keyWindow?.rootViewController = rootController
|
||||
}
|
||||
|
||||
if animatable {
|
||||
UIView.transition(
|
||||
with: UIApplication.shared.keyWindow ?? UIWindow(),
|
||||
duration: 0.5,
|
||||
options: UIView.AnimationOptions.transitionCrossDissolve,
|
||||
animations: {
|
||||
replaceRootViewController()
|
||||
}, completion: nil)
|
||||
} else {
|
||||
replaceRootViewController()
|
||||
}
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
+4
-4
@@ -28,7 +28,7 @@ class SPWelcomeViewController: SPBaseViewController {
|
||||
let subtitleLabel = UILabel()
|
||||
let descriptionLabel = UILabel()
|
||||
let commentLabel = UILabel()
|
||||
let button = SPNativeOS11Button()
|
||||
let button = SPNativeLargeButton()
|
||||
|
||||
private var data: SPWelcomeData
|
||||
private var views: [UIView] = []
|
||||
@@ -134,7 +134,7 @@ class SPWelcomeViewController: SPBaseViewController {
|
||||
|
||||
let sideSpace: CGFloat = size.width * 0.112
|
||||
|
||||
self.imageView.frame = CGRect.init(x: sideSpace, y: self.topSafeArea + (size.height * 0.1), width: 84, height: 84)
|
||||
self.imageView.frame = CGRect.init(x: sideSpace, y: self.safeArea.top + (size.height * 0.1), width: 84, height: 84)
|
||||
|
||||
let space: CGFloat = size.height * 0.025
|
||||
|
||||
@@ -148,7 +148,7 @@ class SPWelcomeViewController: SPBaseViewController {
|
||||
self.descriptionLabel.sizeToFit()
|
||||
|
||||
self.button.sizeToFit()
|
||||
self.button.frame.origin.y = size.height - self.bottomSafeArea - space - self.button.frame.height
|
||||
self.button.frame.origin.y = size.height - self.safeArea.bottom - space - self.button.frame.height
|
||||
self.button.setWidth(size.width - sideSpace * 2)
|
||||
self.button.center.x = size.width / 2
|
||||
|
||||
@@ -214,5 +214,5 @@ struct SPWelcomeData {
|
||||
var backgroundColor: UIColor = UIColor.white
|
||||
var textColor: UIColor = UIColor.black
|
||||
var statusBarStyle: SPStatusBar = .dark
|
||||
var complection: (_ button: SPNativeOS11Button) -> () = { _ in }
|
||||
var complection: (_ button: SPNativeLargeButton) -> () = { _ in }
|
||||
}
|
||||
|
||||
-215
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
+4
-19
@@ -21,27 +21,13 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPDownloadingImageView: UIImageView {
|
||||
class SPDownloadingImageView: SPImageView {
|
||||
|
||||
let activityIndiactorView = UIActivityIndicatorView.init()
|
||||
let gradeView = UIView.init()
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
override init(image: UIImage?) {
|
||||
super.init(image: image)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
func commonInit() {
|
||||
override func commonInit() {
|
||||
super.commonInit()
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.layer.masksToBounds = true
|
||||
self.addSubview(self.gradeView)
|
||||
@@ -76,13 +62,12 @@ class SPDownloadingImageView: UIImageView {
|
||||
}
|
||||
}
|
||||
|
||||
func setLoadingMode() {
|
||||
func startLoading() {
|
||||
self.image = nil
|
||||
self.activityIndiactorView.startAnimating()
|
||||
self.gradeView.alpha = 1
|
||||
}
|
||||
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.gradeView.setEqualsBoundsFromSuperview()
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class SPGradientView: UIView {
|
||||
public class SPGradientView: SPView {
|
||||
|
||||
var startColor: UIColor = UIColor.white { didSet { self.updateGradient() }}
|
||||
var endColor: UIColor = UIColor.black { didSet { self.updateGradient() }}
|
||||
@@ -29,17 +29,7 @@ public class SPGradientView: UIView {
|
||||
var startColorPoint: CGPoint = CGPoint.zero { didSet { self.updateGradient() }}
|
||||
var endColorPoint: CGPoint = CGPoint.zero { didSet { self.updateGradient() }}
|
||||
|
||||
var gradientLayer: CAGradientLayer!
|
||||
|
||||
public init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
var gradientLayer: CAGradientLayer = CAGradientLayer()
|
||||
|
||||
public func setStartColorPosition(_ position: Position) {
|
||||
self.startColorPoint = getPointForPosition(position)
|
||||
@@ -49,16 +39,16 @@ public class SPGradientView: UIView {
|
||||
self.endColorPoint = getPointForPosition(position)
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
self.gradientLayer = CAGradientLayer()
|
||||
self.layer.insertSublayer(self.gradientLayer!, at: 0)
|
||||
override func commonInit() {
|
||||
super.commonInit()
|
||||
self.layer.insertSublayer(self.gradientLayer, at: 0)
|
||||
}
|
||||
|
||||
private func updateGradient() {
|
||||
self.gradientLayer!.colors = [startColor.cgColor, endColor.cgColor]
|
||||
self.gradientLayer!.locations = [0.0, 1.0]
|
||||
self.gradientLayer!.startPoint = self.startColorPoint
|
||||
self.gradientLayer!.endPoint = self.endColorPoint
|
||||
self.gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
|
||||
self.gradientLayer.locations = [0.0, 1.0]
|
||||
self.gradientLayer.startPoint = self.startColorPoint
|
||||
self.gradientLayer.endPoint = self.endColorPoint
|
||||
}
|
||||
|
||||
override public func layoutSublayers(of layer: CALayer) {
|
||||
|
||||
@@ -23,6 +23,23 @@ import UIKit
|
||||
|
||||
class SPScrollView: UIScrollView {
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
internal func commonInit() {
|
||||
if #available(iOS 11.0, *) {
|
||||
self.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
self.delaysContentTouches = false
|
||||
}
|
||||
|
||||
override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
if view is UIControl
|
||||
&& !(view is UITextInput)
|
||||
|
||||
@@ -47,4 +47,3 @@ class ModalViewController: UIViewController {
|
||||
return .lightContent
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<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
|
||||
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
|
||||
|
||||
@@ -16,7 +16,7 @@ The project is absolutely free, but but it takes time to support and update it.
|
||||
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
|
||||
@@ -25,36 +25,65 @@ 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)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Add Navigation Bar
|
||||
You may want to add a navigation bar to your modal controller. Since it became impossible to change or customize the native controller in swift 4 (I couldn’t even find a way to change the height of bar), I completely create navigation bar. Visually, it looks real, but it doesn’t execute the necessary functions
|
||||
### Parametrs
|
||||
#### Upload in next version, now paramtrs no available
|
||||
|
||||
- Parametr `isSwipeToDismissEnabled` enable dissmiss by swipe gester. Defualt is `true`:
|
||||
|
||||
```swift
|
||||
let transitionDelegate = SPStorkTransitioningDelegate()
|
||||
transitionDelegate.isSwipeToDismissEnabled = true
|
||||
```
|
||||
|
||||
- Parametr `showIndicator` show or hide top arrow indicator. Defualt is `true`:
|
||||
```swift
|
||||
transitionDelegate.showIndicator = true
|
||||
```
|
||||
|
||||
### Add Navigation Bar
|
||||
You may want to add a navigation bar to your modal controller. Since it became impossible to change or customize the native controller in swift 4 (I couldn’t even find a way to change the height of bar), I completely create navigation bar. Visually, it looks real, but it doesn’t execute the necessary functions:
|
||||
|
||||
```swift
|
||||
import UIKit
|
||||
|
||||
class ModalController: Controller {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -64,6 +93,22 @@ You only need to add a navigation bar to the main view, it will automatically la
|
||||
|
||||
<img src="https://rawcdn.githack.com/IvanVorobei/SPStorkController/916cfef888b3e70ca45d1b8b26fba1947421632b/Recources/SPStorkController - Banner.jpg"/>
|
||||
|
||||
For use `SPFakeBarView` you need install additional pod:
|
||||
|
||||
```ruby
|
||||
pod 'SparrowKit'
|
||||
```
|
||||
|
||||
### Work with UIScrollView
|
||||
|
||||
If you use `UIScrollView` (or UITableView & UICollectionView) on your controller, I recommend making it more interactive. When the scroll reaches the top position, the controller will interactively drag down, simulating a closing animation. To do this, set the delegate and in the function `scrollViewDidScroll` call:
|
||||
|
||||
```swift
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
SPStorkController.scrollViewDidScroll(scrollView)
|
||||
}
|
||||
```
|
||||
|
||||
## My projects
|
||||
|
||||
Here I would like to offer you my other projects
|
||||
@@ -74,20 +119,16 @@ Project [SPPermission](https://github.com/IvanVorobei/SPPermission) for managing
|
||||
<img src="https://rawcdn.githack.com/IvanVorobei/RequestPermission/fb53d20f152a3e76e053e6af529306611fb794f0/resources/request-permission - mockup_preview.gif" width="500">
|
||||
|
||||
### SparrowKit
|
||||
The `SPStorkController` use [SparrowKit](https://github.com/IvanVorobei/SparrowKit) library. You can install it if you want to receive updates often. Also in library you can find [SPPermission](https://github.com/IvanVorobei/SPPermission) and other useful extensions. For install via CocoaPods use:
|
||||
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'
|
||||
```
|
||||
|
||||
If you use `pod SPStorkController`, library [SparrowKit](https://github.com/IvanVorobei/SparrowKit) install automatically.
|
||||
|
||||
## License
|
||||
`SPStorkController` is released under the MIT license. Check LICENSE.md for details
|
||||
|
||||
## Contact
|
||||
If you need develop application or UI, write me to hello@ivanvorobei.by. I am develop design and ios apps. I am use `swift`. If you want to ask for more functionality, you should create a new issue
|
||||
If you need develop application or UI, write me to hello@ivanvorobei.by. I am develop design and ios apps. I am use `swift`. If you want to ask for more functionality, you should create a new issue.
|
||||
|
||||
[hello.ivanvorobei.by](https://hello.ivanvorobei.by) & [ivanvorobei.by](https://hello.ivanvorobei.by)
|
||||
|
||||
My apps [in AppStore](https://itunes.apple.com/us/developer/polina-zubarik/id1434528595) & [in AppStore 2](https://itunes.apple.com/us/developer/mikalai-varabei/id1435792103)
|
||||
My apps in AppStore: [first account](https://itunes.apple.com/us/developer/polina-zubarik/id1434528595) & [second account](https://itunes.apple.com/us/developer/mikalai-varabei/id1435792103)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SPStorkController"
|
||||
s.version = "1.0.8"
|
||||
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.2'
|
||||
s.source_files = "Source/StorkController/**/*.swift"
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
public struct SPStorkController {
|
||||
|
||||
static func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if let controller = self.controller(for: scrollView) {
|
||||
if let presentationController = controller.presentationController as? SPStorkPresentationController {
|
||||
let translation = -(scrollView.contentOffset.y + scrollView.contentInset.top)
|
||||
if translation >= 0 {
|
||||
scrollView.subviews.forEach {
|
||||
$0.transform = CGAffineTransform(translationX: 0, y: -translation)
|
||||
}
|
||||
if presentationController.pan?.state != UIGestureRecognizer.State.changed {
|
||||
presentationController.scrollViewDidScroll(translation)
|
||||
}
|
||||
} else {
|
||||
presentationController.scrollViewDidScroll(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private func controller(for view: UIView) -> UIViewController? {
|
||||
var nextResponder = view.next
|
||||
while nextResponder != nil && !(nextResponder! is UIViewController) {
|
||||
nextResponder = nextResponder!.next
|
||||
}
|
||||
return nextResponder as? UIViewController
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
|
||||
var isPresentedAsStork: Bool {
|
||||
return transitioningDelegate is SPStorkTransitioningDelegate
|
||||
&& modalPresentationStyle == .custom
|
||||
&& presentingViewController != nil
|
||||
}
|
||||
}
|
||||
+25
-18
@@ -19,27 +19,34 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import SystemConfiguration
|
||||
import UIKit
|
||||
|
||||
public struct SPInternetConnection {
|
||||
|
||||
static var isOpen: Bool {
|
||||
var zeroAddress = sockaddr_in()
|
||||
zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
|
||||
zeroAddress.sin_family = sa_family_t(AF_INET)
|
||||
let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
|
||||
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
|
||||
SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
|
||||
}
|
||||
final class SPStorkDismissingAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
|
||||
guard let presentedViewController = transitionContext.viewController(forKey: .from) else {
|
||||
return
|
||||
}
|
||||
var flags = SCNetworkReachabilityFlags()
|
||||
if !SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) {
|
||||
return false
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
|
||||
UIView.animate(
|
||||
withDuration: transitionDuration(using: transitionContext),
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 1,
|
||||
initialSpringVelocity: 1,
|
||||
options: .curveEaseIn,
|
||||
animations: {
|
||||
presentedViewController.view.frame = offscreenFrame
|
||||
}) { finished in
|
||||
transitionContext.completeTransition(finished)
|
||||
}
|
||||
let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
|
||||
let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
|
||||
return (isReachable && !needsConnection)
|
||||
}
|
||||
|
||||
private init() {}
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
return 0.6
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPStorkIndicatorView: UIView {
|
||||
|
||||
var style: Style = .line {
|
||||
didSet {
|
||||
switch self.style {
|
||||
case .line:
|
||||
self.animate {
|
||||
self.leftView.transform = .identity
|
||||
self.rightView.transform = .identity
|
||||
}
|
||||
case .arrow:
|
||||
self.animate {
|
||||
let angle = CGFloat(20 * Float.pi / 180)
|
||||
self.leftView.transform = CGAffineTransform.init(rotationAngle: angle)
|
||||
self.rightView.transform = CGAffineTransform.init(rotationAngle: -angle)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private var leftView: UIView = UIView()
|
||||
private var rightView: UIView = UIView()
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.addSubview(self.leftView)
|
||||
self.addSubview(self.rightView)
|
||||
self.leftView.backgroundColor = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1)
|
||||
self.rightView.backgroundColor = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func sizeToFit() {
|
||||
super.sizeToFit()
|
||||
self.frame = CGRect.init(x: self.frame.origin.x, y: self.frame.origin.y, width: 36, height: 13)
|
||||
|
||||
let height: CGFloat = 5
|
||||
let correction = height / 2
|
||||
|
||||
self.leftView.frame = CGRect.init(x: 0, y: 0, width: self.frame.width / 2 + correction, height: height)
|
||||
self.leftView.center.y = self.frame.height / 2
|
||||
self.leftView.layer.cornerRadius = min(self.leftView.frame.width, self.leftView.frame.height) / 2
|
||||
|
||||
self.rightView.frame = CGRect.init(x: self.frame.width / 2 - correction, y: 0, width: self.frame.width / 2 + correction, height: height)
|
||||
self.rightView.center.y = self.frame.height / 2
|
||||
self.rightView.layer.cornerRadius = min(self.leftView.frame.width, self.leftView.frame.height) / 2
|
||||
}
|
||||
|
||||
private func animate(animations: @escaping (() -> Void)) {
|
||||
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.beginFromCurrentState, .curveEaseOut], animations: {
|
||||
animations()
|
||||
})
|
||||
}
|
||||
|
||||
enum Style {
|
||||
case arrow
|
||||
case line
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPStorkPresentationController: UIPresentationController, UIGestureRecognizerDelegate {
|
||||
|
||||
var isSwipeToDismissEnabled: Bool = true
|
||||
var showIndicator: Bool = true
|
||||
var transitioningDelegate: SPStorkTransitioningDelegate?
|
||||
var pan: UIPanGestureRecognizer?
|
||||
|
||||
private var indicatorView = SPStorkIndicatorView()
|
||||
private var gradeView: UIView = UIView()
|
||||
private let snapshotViewContainer = UIView()
|
||||
private var snapshotView: UIView?
|
||||
private let backgroundView = UIView()
|
||||
|
||||
private var snapshotViewTopConstraint: NSLayoutConstraint?
|
||||
private var snapshotViewWidthConstraint: NSLayoutConstraint?
|
||||
private var snapshotViewAspectRatioConstraint: NSLayoutConstraint?
|
||||
private var workGester: Bool = false
|
||||
private var startDismissing: Bool = false
|
||||
|
||||
private var topSpace: CGFloat {
|
||||
let statusBarHeight: CGFloat = UIApplication.shared.statusBarFrame.height
|
||||
return (statusBarHeight < 25) ? 30 : statusBarHeight
|
||||
}
|
||||
|
||||
private var alpha: CGFloat {
|
||||
return 0.51
|
||||
}
|
||||
|
||||
private var cornerRadius: CGFloat {
|
||||
return 10
|
||||
}
|
||||
|
||||
private var scaleForPresentingView: CGFloat {
|
||||
guard let containerView = containerView else { return 0 }
|
||||
let factor = 1 - (topSpace * 2 / containerView.frame.height)
|
||||
return factor
|
||||
}
|
||||
|
||||
override var frameOfPresentedViewInContainerView: CGRect {
|
||||
guard let containerView = containerView else { return .zero }
|
||||
let yOffset: CGFloat = topSpace + 13
|
||||
return CGRect(x: 0, y: yOffset, width: containerView.bounds.width, height: containerView.bounds.height - yOffset)
|
||||
}
|
||||
|
||||
override func presentationTransitionWillBegin() {
|
||||
super.presentationTransitionWillBegin()
|
||||
|
||||
guard let containerView = self.containerView, let presentedView = self.presentedView, let window = containerView.window else { return }
|
||||
|
||||
if self.showIndicator {
|
||||
presentedView.addSubview(self.indicatorView)
|
||||
}
|
||||
self.updateLayoutIndicator()
|
||||
self.indicatorView.style = .arrow
|
||||
self.gradeView.alpha = 0
|
||||
|
||||
let initialFrame: CGRect = presentingViewController.isPresentedAsStork ? presentingViewController.view.frame : containerView.bounds
|
||||
|
||||
containerView.insertSubview(self.snapshotViewContainer, belowSubview: presentedViewController.view)
|
||||
self.snapshotViewContainer.frame = initialFrame
|
||||
self.updateSnapshot()
|
||||
self.backgroundView.backgroundColor = UIColor.black
|
||||
self.backgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||
containerView.insertSubview(self.backgroundView, belowSubview: self.snapshotViewContainer)
|
||||
NSLayoutConstraint.activate([
|
||||
self.backgroundView.topAnchor.constraint(equalTo: window.topAnchor),
|
||||
self.backgroundView.leftAnchor.constraint(equalTo: window.leftAnchor),
|
||||
self.backgroundView.rightAnchor.constraint(equalTo: window.rightAnchor),
|
||||
self.backgroundView.bottomAnchor.constraint(equalTo: window.bottomAnchor)
|
||||
])
|
||||
|
||||
let transformForSnapshotView = CGAffineTransform.identity
|
||||
.translatedBy(x: 0, y: -snapshotViewContainer.frame.origin.y)
|
||||
.translatedBy(x: 0, y: self.topSpace)
|
||||
.translatedBy(x: 0, y: -snapshotViewContainer.frame.height / 2)
|
||||
.scaledBy(x: scaleForPresentingView, y: scaleForPresentingView)
|
||||
.translatedBy(x: 0, y: snapshotViewContainer.frame.height / 2)
|
||||
|
||||
self.addCornerRadiusAnimation(for: self.snapshotView, cornerRadius: self.cornerRadius, duration: 0.6)
|
||||
self.snapshotView?.layer.masksToBounds = true
|
||||
if #available(iOS 11.0, *) {
|
||||
presentedView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
}
|
||||
presentedView.layer.cornerRadius = self.cornerRadius
|
||||
presentedView.layer.masksToBounds = true
|
||||
|
||||
var rootSnapshotView: UIView?
|
||||
var rootSnapshotRoundedView: UIView?
|
||||
|
||||
if presentingViewController.isPresentedAsStork {
|
||||
guard let rootController = presentingViewController.presentingViewController, let snapshotView = rootController.view.snapshotView(afterScreenUpdates: false) else { return }
|
||||
|
||||
containerView.insertSubview(snapshotView, aboveSubview: self.backgroundView)
|
||||
snapshotView.frame = initialFrame
|
||||
snapshotView.transform = transformForSnapshotView
|
||||
snapshotView.alpha = self.alpha
|
||||
snapshotView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotView.layer.masksToBounds = true
|
||||
rootSnapshotView = snapshotView
|
||||
|
||||
let snapshotRoundedView = UIView()
|
||||
snapshotRoundedView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotRoundedView.layer.masksToBounds = true
|
||||
containerView.insertSubview(snapshotRoundedView, aboveSubview: snapshotView)
|
||||
snapshotRoundedView.frame = initialFrame
|
||||
snapshotRoundedView.transform = transformForSnapshotView
|
||||
rootSnapshotRoundedView = snapshotRoundedView
|
||||
}
|
||||
|
||||
presentedViewController.transitionCoordinator?.animate(
|
||||
alongsideTransition: { [weak self] context in
|
||||
guard let `self` = self else { return }
|
||||
self.snapshotView?.transform = transformForSnapshotView
|
||||
self.gradeView.alpha = self.alpha
|
||||
}, completion: { _ in
|
||||
self.snapshotView?.transform = .identity
|
||||
rootSnapshotView?.removeFromSuperview()
|
||||
rootSnapshotRoundedView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
override func presentationTransitionDidEnd(_ completed: Bool) {
|
||||
super.presentationTransitionDidEnd(completed)
|
||||
guard let containerView = containerView else { return }
|
||||
self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView
|
||||
self.snapshotViewContainer.transform = .identity
|
||||
self.snapshotViewContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.snapshotViewContainer.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
|
||||
self.updateSnapshotAspectRatio()
|
||||
|
||||
if self.isSwipeToDismissEnabled {
|
||||
self.pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
|
||||
self.pan!.delegate = self
|
||||
self.pan!.maximumNumberOfTouches = 1
|
||||
self.pan!.cancelsTouchesInView = false
|
||||
self.presentedViewController.view.addGestureRecognizer(self.pan!)
|
||||
}
|
||||
}
|
||||
|
||||
override func dismissalTransitionWillBegin() {
|
||||
super.dismissalTransitionWillBegin()
|
||||
guard let containerView = containerView else { return }
|
||||
self.startDismissing = true
|
||||
|
||||
let initialFrame: CGRect = presentingViewController.isPresentedAsStork ? presentingViewController.view.frame : containerView.bounds
|
||||
|
||||
let initialTransform = CGAffineTransform.identity
|
||||
.translatedBy(x: 0, y: -initialFrame.origin.y)
|
||||
.translatedBy(x: 0, y: self.topSpace)
|
||||
.translatedBy(x: 0, y: -initialFrame.height / 2)
|
||||
.scaledBy(x: scaleForPresentingView, y: scaleForPresentingView)
|
||||
.translatedBy(x: 0, y: initialFrame.height / 2)
|
||||
|
||||
self.snapshotViewTopConstraint?.isActive = false
|
||||
self.snapshotViewWidthConstraint?.isActive = false
|
||||
self.snapshotViewAspectRatioConstraint?.isActive = false
|
||||
self.snapshotViewContainer.translatesAutoresizingMaskIntoConstraints = true
|
||||
self.snapshotViewContainer.frame = initialFrame
|
||||
self.snapshotViewContainer.transform = initialTransform
|
||||
|
||||
let finalCornerRadius = presentingViewController.isPresentedAsStork ? self.cornerRadius : 0
|
||||
let finalTransform: CGAffineTransform = .identity
|
||||
|
||||
self.addCornerRadiusAnimation(for: self.snapshotView, cornerRadius: finalCornerRadius, duration: 0.6)
|
||||
|
||||
var rootSnapshotView: UIView?
|
||||
var rootSnapshotRoundedView: UIView?
|
||||
|
||||
if presentingViewController.isPresentedAsStork {
|
||||
guard let rootController = presentingViewController.presentingViewController, let snapshotView = rootController.view.snapshotView(afterScreenUpdates: false) else { return }
|
||||
|
||||
containerView.insertSubview(snapshotView, aboveSubview: backgroundView)
|
||||
snapshotView.frame = initialFrame
|
||||
snapshotView.transform = initialTransform
|
||||
rootSnapshotView = snapshotView
|
||||
snapshotView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotView.layer.masksToBounds = true
|
||||
|
||||
let snapshotRoundedView = UIView()
|
||||
snapshotRoundedView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotRoundedView.layer.masksToBounds = true
|
||||
snapshotRoundedView.backgroundColor = UIColor.black.withAlphaComponent(1 - self.alpha)
|
||||
containerView.insertSubview(snapshotRoundedView, aboveSubview: snapshotView)
|
||||
snapshotRoundedView.frame = initialFrame
|
||||
snapshotRoundedView.transform = initialTransform
|
||||
rootSnapshotRoundedView = snapshotRoundedView
|
||||
}
|
||||
|
||||
presentedViewController.transitionCoordinator?.animate(
|
||||
alongsideTransition: { [weak self] context in
|
||||
guard let `self` = self else { return }
|
||||
self.snapshotView?.transform = .identity
|
||||
self.snapshotViewContainer.transform = finalTransform
|
||||
self.gradeView.alpha = 0
|
||||
}, completion: { _ in
|
||||
rootSnapshotView?.removeFromSuperview()
|
||||
rootSnapshotRoundedView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
override func dismissalTransitionDidEnd(_ completed: Bool) {
|
||||
super.dismissalTransitionDidEnd(completed)
|
||||
guard let containerView = containerView else { return }
|
||||
|
||||
self.backgroundView.removeFromSuperview()
|
||||
self.snapshotView?.removeFromSuperview()
|
||||
self.snapshotViewContainer.removeFromSuperview()
|
||||
|
||||
let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
presentedViewController.view.frame = offscreenFrame
|
||||
presentedViewController.view.transform = .identity
|
||||
}
|
||||
}
|
||||
|
||||
extension SPStorkPresentationController {
|
||||
|
||||
@objc func handlePan(gestureRecognizer: UIPanGestureRecognizer) {
|
||||
guard gestureRecognizer.isEqual(pan), self.isSwipeToDismissEnabled else { return }
|
||||
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
self.workGester = true
|
||||
self.indicatorView.style = .line
|
||||
self.presentingViewController.view.layer.removeAllAnimations()
|
||||
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: containerView)
|
||||
case .changed:
|
||||
self.workGester = true
|
||||
if self.isSwipeToDismissEnabled {
|
||||
let translation = gestureRecognizer.translation(in: presentedView)
|
||||
self.updatePresentedViewForTranslation(inVerticalDirection: translation.y)
|
||||
} else {
|
||||
gestureRecognizer.setTranslation(.zero, in: presentedView)
|
||||
}
|
||||
case .ended:
|
||||
self.workGester = false
|
||||
let translation = gestureRecognizer.translation(in: presentedView).y
|
||||
if translation >= 240 {
|
||||
presentedViewController.dismiss(animated: true, completion: nil)
|
||||
} else {
|
||||
self.indicatorView.style = .arrow
|
||||
UIView.animate(
|
||||
withDuration: 0.6,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 1,
|
||||
initialSpringVelocity: 1,
|
||||
options: [.curveEaseOut, .allowUserInteraction],
|
||||
animations: {
|
||||
self.snapshotView?.transform = .identity
|
||||
self.presentedView?.transform = .identity
|
||||
})
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ translation: CGFloat) {
|
||||
if !self.workGester {
|
||||
self.updatePresentedViewForTranslation(inVerticalDirection: translation)
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePresentedViewForTranslation(inVerticalDirection translation: CGFloat) {
|
||||
if self.startDismissing { return }
|
||||
|
||||
let elasticThreshold: CGFloat = 120
|
||||
let translationFactor: CGFloat = 1 / 2
|
||||
|
||||
if translation >= 0 {
|
||||
let translationForModal: CGFloat = {
|
||||
if translation >= elasticThreshold {
|
||||
let frictionLength = translation - elasticThreshold
|
||||
let frictionTranslation = 30 * atan(frictionLength / 120) + frictionLength / 10
|
||||
return frictionTranslation + (elasticThreshold * translationFactor)
|
||||
} else {
|
||||
return translation * translationFactor
|
||||
}
|
||||
}()
|
||||
|
||||
self.presentedView?.transform = CGAffineTransform(translationX: 0, y: translationForModal)
|
||||
|
||||
if !self.presentingViewController.isPresentedAsStork {
|
||||
let factor = 1 + (translationForModal / 6000)
|
||||
self.snapshotView?.transform = CGAffineTransform.init(scaleX: factor, y: factor)
|
||||
self.gradeView.alpha = self.alpha - ((factor - 1) * 15)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SPStorkPresentationController {
|
||||
|
||||
override func containerViewWillLayoutSubviews() {
|
||||
super.containerViewWillLayoutSubviews()
|
||||
guard let containerView = containerView else { return }
|
||||
self.updateSnapshotAspectRatio()
|
||||
if presentedViewController.view.isDescendant(of: containerView) {
|
||||
UIView.animate(withDuration: 0.1) { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
coordinator.animate(alongsideTransition: { contex in
|
||||
self.updateLayoutIndicator()
|
||||
}, completion: { [weak self] _ in
|
||||
self?.updateSnapshotAspectRatio()
|
||||
self?.updateSnapshot()
|
||||
})
|
||||
}
|
||||
|
||||
private func updateLayoutIndicator() {
|
||||
guard let presentedView = self.presentedView else { return }
|
||||
self.indicatorView.style = .line
|
||||
self.indicatorView.sizeToFit()
|
||||
self.indicatorView.frame.origin.y = 12
|
||||
self.indicatorView.center.x = presentedView.frame.width / 2
|
||||
}
|
||||
|
||||
private func updateSnapshot() {
|
||||
guard let currentSnapshotView = presentingViewController.view.snapshotView(afterScreenUpdates: true) else { return }
|
||||
self.snapshotView?.removeFromSuperview()
|
||||
self.snapshotViewContainer.addSubview(currentSnapshotView)
|
||||
self.constraints(view: currentSnapshotView, to: self.snapshotViewContainer)
|
||||
self.snapshotView = currentSnapshotView
|
||||
self.gradeView.removeFromSuperview()
|
||||
self.gradeView.backgroundColor = UIColor.black
|
||||
self.snapshotView!.addSubview(self.gradeView)
|
||||
self.constraints(view: self.gradeView, to: self.snapshotView!)
|
||||
}
|
||||
|
||||
private func updateSnapshotAspectRatio() {
|
||||
guard let containerView = containerView, snapshotViewContainer.translatesAutoresizingMaskIntoConstraints == false else { return }
|
||||
|
||||
self.snapshotViewTopConstraint?.isActive = false
|
||||
self.snapshotViewWidthConstraint?.isActive = false
|
||||
self.snapshotViewAspectRatioConstraint?.isActive = false
|
||||
|
||||
let snapshotReferenceSize = presentingViewController.view.frame.size
|
||||
let aspectRatio = snapshotReferenceSize.width / snapshotReferenceSize.height
|
||||
|
||||
self.snapshotViewTopConstraint = snapshotViewContainer.topAnchor.constraint(equalTo: containerView.topAnchor, constant: self.topSpace)
|
||||
self.snapshotViewWidthConstraint = snapshotViewContainer.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: scaleForPresentingView)
|
||||
self.snapshotViewAspectRatioConstraint = snapshotViewContainer.widthAnchor.constraint(equalTo: snapshotViewContainer.heightAnchor, multiplier: aspectRatio)
|
||||
|
||||
self.snapshotViewTopConstraint?.isActive = true
|
||||
self.snapshotViewWidthConstraint?.isActive = true
|
||||
self.snapshotViewAspectRatioConstraint?.isActive = true
|
||||
}
|
||||
|
||||
private func constraints(view: UIView, to superView: UIView) {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
view.topAnchor.constraint(equalTo: superView.topAnchor),
|
||||
view.leftAnchor.constraint(equalTo: superView.leftAnchor),
|
||||
view.rightAnchor.constraint(equalTo: superView.rightAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: superView.bottomAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
private func addCornerRadiusAnimation(for view: UIView?, cornerRadius: CGFloat, duration: CFTimeInterval) {
|
||||
guard let view = view else { return }
|
||||
let animation = CABasicAnimation(keyPath:"cornerRadius")
|
||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
|
||||
animation.fromValue = view.layer.cornerRadius
|
||||
animation.toValue = cornerRadius
|
||||
animation.duration = duration
|
||||
view.layer.add(animation, forKey: "cornerRadius")
|
||||
view.layer.cornerRadius = cornerRadius
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
final class SPStorkPresentingAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
|
||||
guard let presentedViewController = transitionContext.viewController(forKey: .to) else {
|
||||
return
|
||||
}
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
containerView.addSubview(presentedViewController.view)
|
||||
presentedViewController.view.frame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
|
||||
let finalFrameForPresentedView = transitionContext.finalFrame(for: presentedViewController)
|
||||
|
||||
UIView.animate(
|
||||
withDuration: transitionDuration(using: transitionContext),
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 1,
|
||||
initialSpringVelocity: 1,
|
||||
options: .curveEaseOut,
|
||||
animations: {
|
||||
presentedViewController.view.frame = finalFrameForPresentedView
|
||||
}, completion: { finished in
|
||||
transitionContext.completeTransition(finished)
|
||||
})
|
||||
}
|
||||
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
return 0.6
|
||||
}
|
||||
}
|
||||
|
||||
+13
-16
@@ -21,25 +21,22 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPScrollViewController: SPBaseViewController {
|
||||
public final class SPStorkTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
|
||||
|
||||
let scrollView = SPScrollView()
|
||||
var isSwipeToDismissEnabled: Bool = true
|
||||
var showIndicator: Bool = true
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
self.view.backgroundColor = SPNativeStyleKit.Colors.customGray
|
||||
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.showsVerticalScrollIndicator = true
|
||||
self.scrollView.indicatorStyle = .black
|
||||
self.view.addSubview(self.scrollView)
|
||||
|
||||
super.viewDidLoad()
|
||||
public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
|
||||
let controller = SPStorkPresentationController(presentedViewController: presented, presenting: presenting)
|
||||
controller.transitioningDelegate = self
|
||||
return controller
|
||||
}
|
||||
|
||||
override func updateLayout(with size: CGSize) {
|
||||
self.scrollView.frame = CGRect.init(origin: .zero, size: size)
|
||||
self.scrollView.contentSize = CGSize.init(width: size.width, height: size.height * 2)
|
||||
super.updateLayout(with: size)
|
||||
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
return SPStorkPresentingAnimationController()
|
||||
}
|
||||
|
||||
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
return SPStorkDismissingAnimationController()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user