Compare commits
174 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b8fa51706 | |||
| 1f1aa2ea8d | |||
| 914ce0f689 | |||
| 5dfab81504 | |||
| 9270185bf6 | |||
| 43bc6c27ee | |||
| 551ccf3bf1 | |||
| 0488521869 | |||
| 9eae918f9d | |||
| 28ed6b6ebc | |||
| b783329b69 | |||
| 84256152dc | |||
| ac366c84fd | |||
| 64bebe0645 | |||
| 1efa55aefb | |||
| e5371ca20c | |||
| fdbc1e3527 | |||
| 306547671c | |||
| 1fede9eb64 | |||
| 3ef2ff81bc | |||
| 83330bb509 | |||
| c4da6ff27e | |||
| 686bbd749b | |||
| 7b151fd268 | |||
| 7bdf702703 | |||
| 0d1381d941 | |||
| 39c30a030d | |||
| 9e4f6e086f | |||
| 5bc31cdc57 | |||
| 7d53ed31e6 | |||
| 80806de694 | |||
| 8d8458cc34 | |||
| 6e09df910e | |||
| 253f60cfa9 | |||
| 81ed7b01cd | |||
| d0f120d49c | |||
| bc3555e715 | |||
| 432a1206cf | |||
| 488b19fc46 | |||
| edc2b6ea41 | |||
| 79d64599d0 | |||
| 854ab2aef2 | |||
| 04c4929e7d | |||
| 6e4e9713c3 | |||
| e37c43c8f3 | |||
| 001536c835 | |||
| 5a7d01e1e4 | |||
| a1d17d994c | |||
| f56a054b91 | |||
| bd26c4d721 | |||
| 6927d90572 | |||
| ea32992f63 | |||
| 05ba026c1f | |||
| b3f49a2a94 | |||
| c3aa4dc17e | |||
| d9f67b57f8 | |||
| 962d1a937d | |||
| bc6c7ff45e | |||
| ec7abd4a2a | |||
| 6904e0916a | |||
| 90ee3d35da | |||
| 7f998787a4 | |||
| 618da7d9ea | |||
| f062d07f94 | |||
| 926e1f643c | |||
| cdd8f4a2f4 | |||
| 30cf10c595 | |||
| 884791b05c | |||
| 4d0530e0a1 | |||
| 48741988b2 | |||
| 6297d5a5db | |||
| 3f60e48547 | |||
| 2ffe86afb9 | |||
| cf5bf12513 | |||
| 96aa1a6064 | |||
| 3241abd195 | |||
| b72f10c7bd | |||
| 40bcff3cb6 | |||
| 01cc226253 | |||
| 691045d012 | |||
| 59f52f258a | |||
| bfece3194a | |||
| 6cccf24ffa | |||
| 635878e456 | |||
| d839e0f414 | |||
| e3cbc318be | |||
| e4232ec045 | |||
| 02346c0814 | |||
| 0b98593954 | |||
| 518700f9cd | |||
| 5e02985ef2 | |||
| 539589e029 | |||
| 3434c487cd | |||
| c18e72add2 | |||
| c5e407fd80 | |||
| 3937c9caa5 | |||
| 38332bed39 | |||
| 40c7eee5de | |||
| 2e881179ee | |||
| 749640d359 | |||
| fd68fcb9f5 | |||
| 473f8ae0f1 | |||
| 75825b5e0a | |||
| f1e053d0d3 | |||
| 6208c93d40 | |||
| 8c64cd09da | |||
| c36078cda2 | |||
| b78c82194f | |||
| a112473a04 | |||
| c1b135a30a | |||
| c1a7622a26 | |||
| c3421432b0 | |||
| 99353d3610 | |||
| 3bee6898e0 | |||
| 587790b1db | |||
| 2698cfe23d | |||
| 42b82886ca | |||
| 7af77b2596 | |||
| 7c663e7760 | |||
| f561e94110 | |||
| 3bf06ae2b3 | |||
| a6d5e6f97d | |||
| 0f20afb87c | |||
| 2368747150 | |||
| 32a010e1ca | |||
| fce0efaffa | |||
| 1500c272b1 | |||
| ba859e3646 | |||
| 01b8702c4a | |||
| ec809ee1c5 | |||
| a5f41ab3d6 | |||
| 58554f1449 | |||
| 8c1b6e2c8d | |||
| 724635e387 | |||
| dfc0fb8e52 | |||
| 50c9ab4104 | |||
| bb0821d2cc | |||
| ae29eba5b9 | |||
| 0c98c3c699 | |||
| 4ecc8e96d8 | |||
| fac454f41d | |||
| ae68a1be5d | |||
| c111a20c86 | |||
| cc1b1ac604 | |||
| 265f77dd5e | |||
| aacf153d0e | |||
| 5b8fdf6590 | |||
| 883ade3052 | |||
| 44837098ea | |||
| 42063749ed | |||
| 464df2eb5c | |||
| 6f48df5761 | |||
| e331793afc | |||
| 82152b698a | |||
| 75eaaec598 | |||
| d2cc2e424c | |||
| 0ac35b4494 | |||
| 6c2600dea8 | |||
| 26d7422216 | |||
| a4a02b598f | |||
| 0309bb2e0e | |||
| 4e0507f132 | |||
| 8e33ff7614 | |||
| b7ab9e0327 | |||
| 4129f23d00 | |||
| 91b01d83d9 | |||
| 4eda691bd0 | |||
| 0889352299 | |||
| 34f267d43c | |||
| 425f8ec1ca | |||
| 25ea7eab4c | |||
| 16f060820f | |||
| 4c8faf7695 | |||
| 5b843582d4 |
+37
@@ -1 +1,38 @@
|
||||
# Mac OS X
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
## Build generated
|
||||
build/
|
||||
DerivedData
|
||||
|
||||
## Various settings
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
|
||||
## Other
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
*.xcuserstate
|
||||
*.xcscmblueprint
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
*.ipa
|
||||
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
|
||||
# Swift Package Manager
|
||||
.build/
|
||||
|
||||
# Carthage
|
||||
Carthage/Build
|
||||
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
@@ -7,6 +7,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
|
||||
let navigationController = UINavigationController(rootViewController: Controller())
|
||||
self.launch(rootViewController: navigationController)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -32,6 +36,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
func launch(rootViewController: UIViewController) {
|
||||
let frame = UIScreen.main.bounds
|
||||
self.window = UIWindow(frame: frame)
|
||||
self.window!.rootViewController = rootViewController
|
||||
self.window!.makeKeyAndVisible()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,44 @@
|
||||
import UIKit
|
||||
|
||||
class Controller: UIViewController {
|
||||
|
||||
var presentControllerButton = UIButton.init(type: UIButton.ButtonType.system)
|
||||
var presentTableControllerButton = UIButton.init(type: UIButton.ButtonType.system)
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.view.backgroundColor = UIColor.white
|
||||
|
||||
self.presentControllerButton.setTitle("Show ViewController", for: .normal)
|
||||
self.presentControllerButton.addTarget(self, action: #selector(self.presentModalViewController), for: .touchUpInside)
|
||||
self.presentControllerButton.sizeToFit()
|
||||
self.presentControllerButton.center.x = self.view.frame.width / 2
|
||||
self.presentControllerButton.center.y = self.view.frame.height / 4 * 3
|
||||
self.view.addSubview(self.presentControllerButton)
|
||||
|
||||
self.presentTableControllerButton.setTitle("Show TableController", for: .normal)
|
||||
self.presentTableControllerButton.addTarget(self, action: #selector(self.presentModalTableViewController), for: .touchUpInside)
|
||||
self.presentTableControllerButton.sizeToFit()
|
||||
self.presentTableControllerButton.center.x = self.view.frame.width / 2
|
||||
self.presentTableControllerButton.frame.origin.y = self.presentControllerButton.frame.bottomY + 10
|
||||
self.view.addSubview(self.presentTableControllerButton)
|
||||
}
|
||||
|
||||
@objc func presentModalViewController() {
|
||||
let modal = ModalViewController()
|
||||
let transitionDelegate = SPStorkTransitioningDelegate()
|
||||
modal.transitioningDelegate = transitionDelegate
|
||||
transitionDelegate.hapticMoments
|
||||
modal.modalPresentationStyle = .custom
|
||||
self.present(modal, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc func presentModalTableViewController() {
|
||||
let modal = ModalTableViewController()
|
||||
let transitionDelegate = SPStorkTransitioningDelegate()
|
||||
modal.transitioningDelegate = transitionDelegate
|
||||
modal.modalPresentationStyle = .custom
|
||||
self.present(modal, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
+5
-2
@@ -21,7 +21,10 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public struct SPStyleKit {
|
||||
public enum SPFakeBarNavigationStyle {
|
||||
|
||||
private init() {}
|
||||
case large
|
||||
case small
|
||||
case stork
|
||||
case noContent
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
// 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
|
||||
|
||||
open class SPFakeBarView: UIView {
|
||||
|
||||
public var style: SPFakeBarNavigationStyle = .small {
|
||||
didSet {
|
||||
self.updateStyle()
|
||||
}
|
||||
}
|
||||
|
||||
private var settedHeight: CGFloat = 0
|
||||
public var height: CGFloat {
|
||||
get {
|
||||
return (self.settedHeight) + (self.addStatusBarHeight ? UIApplication.shared.statusBarFrame.height : 0)
|
||||
}
|
||||
set {
|
||||
self.settedHeight = newValue
|
||||
self.updateHeight()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public var addStatusBarHeight: Bool = true {
|
||||
didSet {
|
||||
self.updateHeight()
|
||||
}
|
||||
}
|
||||
|
||||
public var elementsColor: UIColor = SPFakeBarView.navigationElementsColor {
|
||||
didSet {
|
||||
self.leftButton.setTitleColor(self.elementsColor, for: .normal)
|
||||
self.leftButton.setTitleColor(self.elementsColor.withAlphaComponent(0.7), for: .highlighted)
|
||||
self.rightButton.setTitleColor(self.elementsColor, for: .normal)
|
||||
self.rightButton.setTitleColor(self.elementsColor.withAlphaComponent(0.7), for: .highlighted)
|
||||
}
|
||||
}
|
||||
|
||||
public var closeButtonPossition: CloseButtonPosition = .none {
|
||||
didSet {
|
||||
self.leftButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .regular)
|
||||
self.rightButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .regular)
|
||||
switch self.closeButtonPossition {
|
||||
case .left:
|
||||
self.leftButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
|
||||
case .right:
|
||||
self.rightButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var titleLabel = UILabel.init()
|
||||
public var subtitleLabel = UILabel.init()
|
||||
public var leftButton = UIButton.init()
|
||||
public var rightButton = UIButton.init()
|
||||
|
||||
public let separatorView = UIView()
|
||||
public let blurView: UIVisualEffectView = {
|
||||
let effect = UIBlurEffect(style: .extraLight)
|
||||
return UIVisualEffectView.init(effect: effect)
|
||||
}()
|
||||
|
||||
private var titleBottomConstraint: NSLayoutConstraint?
|
||||
private var heightConstraint: NSLayoutConstraint?
|
||||
private var topConstraint: NSLayoutConstraint?
|
||||
private var leadingConstraint: NSLayoutConstraint?
|
||||
private var trailingConstraint: NSLayoutConstraint?
|
||||
|
||||
public init(style: SPFakeBarNavigationStyle) {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.style = style
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.style = .small
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
self.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
self.addSubview(self.blurView)
|
||||
self.blurView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.blurView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
|
||||
self.blurView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
|
||||
self.blurView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
|
||||
self.blurView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
|
||||
|
||||
self.addSubview(self.separatorView)
|
||||
self.separatorView.backgroundColor = UIColor.init(red: 191 / 255.0, green: 191 / 255.0, blue: 191 / 255.0, alpha: 1)
|
||||
self.separatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.separatorView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
|
||||
self.separatorView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
|
||||
self.separatorView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
|
||||
self.separatorView.heightAnchor.constraint(equalToConstant: 0.5).isActive = true
|
||||
|
||||
self.addSubview(self.titleLabel)
|
||||
self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16).isActive = true
|
||||
self.titleLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16).isActive = true
|
||||
self.titleBottomConstraint = self.titleLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -12)
|
||||
self.titleBottomConstraint?.isActive = true
|
||||
|
||||
self.addSubview(self.subtitleLabel)
|
||||
self.subtitleLabel.textColor = UIColor.init(red: 142 / 255.0, green: 142 / 255.0, blue: 146 / 255.0, alpha: 1)
|
||||
self.subtitleLabel.font = UIFont.systemFont(ofSize: 13, weight: .semibold)
|
||||
self.subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.subtitleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16).isActive = true
|
||||
self.subtitleLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16).isActive = true
|
||||
self.subtitleLabel.bottomAnchor.constraint(equalTo: self.titleLabel.topAnchor, constant: 0).isActive = true
|
||||
|
||||
self.leftButton.setTitleColor(self.elementsColor, for: .normal)
|
||||
self.leftButton.setTitleColor(self.elementsColor.withAlphaComponent(0.7), for: .highlighted)
|
||||
self.leftButton.titleLabel?.textAlignment = .left
|
||||
self.leftButton.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
|
||||
self.leftButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 17, bottom: 0, right: 0)
|
||||
self.addSubview(self.leftButton)
|
||||
self.leftButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.leftButton.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
|
||||
self.leftButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -12).isActive = true
|
||||
|
||||
self.rightButton.setTitleColor(self.elementsColor, for: .normal)
|
||||
self.rightButton.setTitleColor(self.elementsColor.withAlphaComponent(0.7), for: .highlighted)
|
||||
self.rightButton.titleLabel?.textAlignment = .right
|
||||
self.rightButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
|
||||
self.rightButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16)
|
||||
self.addSubview(self.rightButton)
|
||||
self.rightButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.rightButton.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
|
||||
self.rightButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -12).isActive = true
|
||||
|
||||
self.closeButtonPossition = .none
|
||||
|
||||
self.setContraints()
|
||||
self.updateStyle()
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.setContraints()
|
||||
}
|
||||
|
||||
private func setContraints() {
|
||||
if let superview = self.superview {
|
||||
if self.topConstraint == nil {
|
||||
|
||||
self.topConstraint = self.topAnchor.constraint(equalTo: superview.topAnchor)
|
||||
self.topConstraint?.isActive = true
|
||||
self.leadingConstraint = self.leadingAnchor.constraint(equalTo: superview.leadingAnchor)
|
||||
self.leadingConstraint?.isActive = true
|
||||
self.trailingConstraint = self.trailingAnchor.constraint(equalTo: superview.trailingAnchor)
|
||||
self.trailingConstraint?.isActive = true
|
||||
|
||||
self.heightConstraint = self.heightAnchor.constraint(equalToConstant: self.height)
|
||||
self.heightConstraint?.isActive = true
|
||||
self.updateHeight()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateStyle() {
|
||||
switch self.style {
|
||||
case .small:
|
||||
if UIApplication.shared.statusBarFrame.height == 44 {
|
||||
self.height = 88 - 44
|
||||
self.titleBottomConstraint?.constant = -12
|
||||
} else {
|
||||
self.height = 64 - 20
|
||||
self.titleBottomConstraint?.constant = -12
|
||||
}
|
||||
self.addStatusBarHeight = true
|
||||
self.titleLabel.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
|
||||
self.titleLabel.textAlignment = .center
|
||||
case .stork:
|
||||
self.height = 66
|
||||
self.titleBottomConstraint?.constant = -12
|
||||
self.addStatusBarHeight = false
|
||||
self.titleLabel.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
|
||||
self.titleLabel.textAlignment = .center
|
||||
case .large:
|
||||
if UIApplication.shared.statusBarFrame.height == 44 {
|
||||
self.height = 140 - 44
|
||||
self.titleBottomConstraint?.constant = -8
|
||||
} else {
|
||||
self.height = 116 - 20
|
||||
self.titleBottomConstraint?.constant = -4
|
||||
}
|
||||
self.addStatusBarHeight = true
|
||||
self.titleLabel.font = UIFont.systemFont(ofSize: 34, weight: .bold)
|
||||
self.titleLabel.textAlignment = .left
|
||||
break
|
||||
case .noContent:
|
||||
self.height = 0
|
||||
self.addStatusBarHeight = true
|
||||
}
|
||||
|
||||
self.updateConstraints()
|
||||
}
|
||||
|
||||
private func updateHeight() {
|
||||
self.heightConstraint?.constant = self.height
|
||||
self.updateConstraints()
|
||||
}
|
||||
|
||||
public enum CloseButtonPosition {
|
||||
case left
|
||||
case right
|
||||
case none
|
||||
}
|
||||
}
|
||||
|
||||
extension SPFakeBarView {
|
||||
|
||||
static var navigationElementsColor: UIColor {
|
||||
get {
|
||||
if UINavigationBar.appearance().tintColor != nil {
|
||||
return UINavigationBar.appearance().tintColor
|
||||
} else {
|
||||
return UIColor.init(red: 0 / 255.0, green: 122 / 255.0, blue: 255 / 255.0, alpha: 1)
|
||||
}
|
||||
}
|
||||
set {
|
||||
UINavigationBar.appearance().tintColor = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import UIKit
|
||||
|
||||
class SPStorkCodeDraw : NSObject {
|
||||
|
||||
private struct Cache {
|
||||
static let gradient: CGGradient = CGGradient(colorsSpace: nil, colors: [UIColor.red.cgColor, UIColor.red.cgColor] as CFArray, locations: [0, 1])!
|
||||
}
|
||||
|
||||
@objc dynamic class var gradient: CGGradient { return Cache.gradient }
|
||||
|
||||
@objc dynamic class func drawClose(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
context.saveGState()
|
||||
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 100, height: 100), target: targetFrame)
|
||||
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
|
||||
context.scaleBy(x: resizedFrame.width / 100, y: resizedFrame.height / 100)
|
||||
|
||||
let bezierPath = UIBezierPath()
|
||||
bezierPath.move(to: CGPoint(x: 92.02, y: 22.92))
|
||||
bezierPath.addLine(to: CGPoint(x: 64.42, y: 50.52))
|
||||
bezierPath.addLine(to: CGPoint(x: 92.02, y: 78.13))
|
||||
bezierPath.addCurve(to: CGPoint(x: 92.02, y: 92.99), controlPoint1: CGPoint(x: 96.13, y: 82.23), controlPoint2: CGPoint(x: 96.13, y: 88.89))
|
||||
bezierPath.addCurve(to: CGPoint(x: 84.59, y: 96.07), controlPoint1: CGPoint(x: 89.97, y: 95.05), controlPoint2: CGPoint(x: 87.28, y: 96.07))
|
||||
bezierPath.addCurve(to: CGPoint(x: 77.16, y: 92.99), controlPoint1: CGPoint(x: 81.9, y: 96.07), controlPoint2: CGPoint(x: 79.22, y: 95.05))
|
||||
bezierPath.addLine(to: CGPoint(x: 49.55, y: 65.38))
|
||||
bezierPath.addLine(to: CGPoint(x: 21.95, y: 92.99))
|
||||
bezierPath.addCurve(to: CGPoint(x: 14.51, y: 96.07), controlPoint1: CGPoint(x: 19.89, y: 95.05), controlPoint2: CGPoint(x: 17.2, y: 96.07))
|
||||
bezierPath.addCurve(to: CGPoint(x: 7.08, y: 92.99), controlPoint1: CGPoint(x: 11.82, y: 96.07), controlPoint2: CGPoint(x: 9.13, y: 95.05))
|
||||
bezierPath.addCurve(to: CGPoint(x: 7.08, y: 78.13), controlPoint1: CGPoint(x: 2.97, y: 88.89), controlPoint2: CGPoint(x: 2.97, y: 82.23))
|
||||
bezierPath.addLine(to: CGPoint(x: 34.69, y: 50.52))
|
||||
bezierPath.addLine(to: CGPoint(x: 7.08, y: 22.92))
|
||||
bezierPath.addCurve(to: CGPoint(x: 7.08, y: 8.04), controlPoint1: CGPoint(x: 2.97, y: 18.8), controlPoint2: CGPoint(x: 2.97, y: 12.15))
|
||||
bezierPath.addCurve(to: CGPoint(x: 21.94, y: 8.04), controlPoint1: CGPoint(x: 11.18, y: 3.94), controlPoint2: CGPoint(x: 17.84, y: 3.94))
|
||||
bezierPath.addLine(to: CGPoint(x: 49.55, y: 35.65))
|
||||
bezierPath.addLine(to: CGPoint(x: 77.16, y: 8.04))
|
||||
bezierPath.addCurve(to: CGPoint(x: 92.02, y: 8.04), controlPoint1: CGPoint(x: 81.26, y: 3.94), controlPoint2: CGPoint(x: 87.92, y: 3.94))
|
||||
bezierPath.addCurve(to: CGPoint(x: 92.02, y: 22.92), controlPoint1: CGPoint(x: 96.13, y: 12.15), controlPoint2: CGPoint(x: 96.13, y: 18.8))
|
||||
bezierPath.close()
|
||||
color.setFill()
|
||||
bezierPath.fill()
|
||||
|
||||
context.restoreGState()
|
||||
|
||||
}
|
||||
|
||||
@objc(StyleKitNameResizingBehavior)
|
||||
enum ResizingBehavior: Int {
|
||||
case aspectFit
|
||||
case aspectFill
|
||||
case stretch
|
||||
case center
|
||||
|
||||
func apply(rect: CGRect, target: CGRect) -> CGRect {
|
||||
if rect == target || target == CGRect.zero {
|
||||
return rect
|
||||
}
|
||||
|
||||
var scales = CGSize.zero
|
||||
scales.width = abs(target.width / rect.width)
|
||||
scales.height = abs(target.height / rect.height)
|
||||
|
||||
switch self {
|
||||
case .aspectFit:
|
||||
scales.width = min(scales.width, scales.height)
|
||||
scales.height = scales.width
|
||||
case .aspectFill:
|
||||
scales.width = max(scales.width, scales.height)
|
||||
scales.height = scales.width
|
||||
case .stretch:
|
||||
break
|
||||
case .center:
|
||||
scales.width = 1
|
||||
scales.height = 1
|
||||
}
|
||||
|
||||
var result = rect.standardized
|
||||
result.size.width *= scales.width
|
||||
result.size.height *= scales.height
|
||||
result.origin.x = target.minX + (target.width - result.width) / 2
|
||||
result.origin.y = target.minY + (target.height - result.height) / 2
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private override init() {}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
// 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
|
||||
|
||||
extension UIViewController {
|
||||
|
||||
public var isPresentedAsStork: Bool {
|
||||
return transitioningDelegate is SPStorkTransitioningDelegate
|
||||
&& modalPresentationStyle == .custom
|
||||
&& presentingViewController != nil
|
||||
}
|
||||
|
||||
public func presentAsStork(_ controller: UIViewController, height: CGFloat? = nil) {
|
||||
let transitionDelegate = SPStorkTransitioningDelegate()
|
||||
transitionDelegate.customHeight = height
|
||||
controller.transitioningDelegate = transitionDelegate
|
||||
controller.modalPresentationStyle = .custom
|
||||
controller.modalPresentationCapturesStatusBarAppearance = true
|
||||
self.present(controller, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
public func presentAsStork(_ controller: UIViewController, height: CGFloat?, showIndicator: Bool, hideIndicatorWhenScroll: Bool, showCloseButton: Bool, complection: (() -> Void)?) {
|
||||
let transitionDelegate = SPStorkTransitioningDelegate()
|
||||
transitionDelegate.customHeight = height
|
||||
transitionDelegate.showCloseButton = showCloseButton
|
||||
transitionDelegate.showIndicator = showIndicator
|
||||
transitionDelegate.hideIndicatorWhenScroll = hideIndicatorWhenScroll
|
||||
controller.transitioningDelegate = transitionDelegate
|
||||
controller.modalPresentationStyle = .custom
|
||||
controller.modalPresentationCapturesStatusBarAppearance = true
|
||||
self.present(controller, animated: true, completion: complection)
|
||||
}
|
||||
}
|
||||
+5
-2
@@ -21,6 +21,9 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
func + (left: CGPoint, right: CGPoint) -> CGPoint {
|
||||
return CGPoint(x: left.x + right.x, y: left.y + right.y)
|
||||
public enum SPStorkHapticMoments {
|
||||
|
||||
case willPresent
|
||||
case willDismiss
|
||||
case willDismissIfRelease
|
||||
}
|
||||
+3
-7
@@ -21,13 +21,9 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UIDevice {
|
||||
@objc public protocol SPStorkControllerDelegate: class {
|
||||
|
||||
public var isIphone: Bool {
|
||||
return UIDevice.current.userInterfaceIdiom == .phone
|
||||
}
|
||||
@objc optional func didDismissStorkBySwipe()
|
||||
|
||||
public var isIpad: Bool {
|
||||
return UIDevice.current.userInterfaceIdiom == .pad
|
||||
}
|
||||
@objc optional func didDismissStorkByTap()
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// 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 public 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 {
|
||||
if controller.isBeingPresented { return }
|
||||
scrollView.subviews.forEach {
|
||||
$0.transform = CGAffineTransform(translationX: 0, y: -translation)
|
||||
}
|
||||
presentationController.setIndicator(style: scrollView.isTracking ? .line : .arrow)
|
||||
if translation >= presentationController.translateForDismiss * 0.4 {
|
||||
if !scrollView.isTracking && !scrollView.isDragging {
|
||||
presentationController.presentedViewController.dismiss(animated: true, completion: {
|
||||
presentationController.storkDelegate?.didDismissStorkBySwipe?()
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
if presentationController.pan?.state != UIGestureRecognizer.State.changed {
|
||||
presentationController.scrollViewDidScroll(translation * 2)
|
||||
}
|
||||
} else {
|
||||
presentationController.setIndicator(style: .arrow)
|
||||
presentationController.scrollViewDidScroll(0)
|
||||
}
|
||||
|
||||
if translation < -5 {
|
||||
presentationController.setIndicator(visible: false, forse: (translation < -50))
|
||||
} else {
|
||||
presentationController.setIndicator(visible: true, forse: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static var topScrollIndicatorInset: CGFloat {
|
||||
return 6
|
||||
}
|
||||
|
||||
static public func updatePresentingController(parent controller: UIViewController) {
|
||||
if let presentationController = controller.presentedViewController?.presentationController as? SPStorkPresentationController {
|
||||
presentationController.updatePresentingController()
|
||||
}
|
||||
}
|
||||
|
||||
static public func updatePresentingController(modal controller: UIViewController) {
|
||||
if let presentationController = controller.presentationController as? SPStorkPresentationController {
|
||||
presentationController.updatePresentingController()
|
||||
}
|
||||
}
|
||||
|
||||
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() {}
|
||||
}
|
||||
+4
-2
@@ -28,9 +28,11 @@ final class SPStorkDismissingAnimationController: NSObject, UIViewControllerAnim
|
||||
guard let presentedViewController = transitionContext.viewController(forKey: .from) else {
|
||||
return
|
||||
}
|
||||
|
||||
let finalFrameForPresentedView = transitionContext.finalFrame(for: presentedViewController)
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: finalFrameForPresentedView.width, height: finalFrameForPresentedView.height)
|
||||
|
||||
UIView.animate(
|
||||
withDuration: transitionDuration(using: transitionContext),
|
||||
@@ -41,7 +43,7 @@ final class SPStorkDismissingAnimationController: NSObject, UIViewControllerAnim
|
||||
animations: {
|
||||
presentedViewController.view.frame = offscreenFrame
|
||||
}) { finished in
|
||||
transitionContext.completeTransition(finished)
|
||||
transitionContext.completeTransition(finished)
|
||||
}
|
||||
}
|
||||
|
||||
+520
@@ -0,0 +1,520 @@
|
||||
// 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 swipeToDismissEnabled: Bool = true
|
||||
var tapAroundToDismissEnabled: Bool = true
|
||||
var showCloseButton: Bool = false
|
||||
var showIndicator: Bool = true
|
||||
var indicatorColor: UIColor = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1)
|
||||
var hideIndicatorWhenScroll: Bool = false
|
||||
var customHeight: CGFloat? = nil
|
||||
var translateForDismiss: CGFloat = 200
|
||||
var hapticMoments: [SPStorkHapticMoments] = [.willDismissIfRelease]
|
||||
|
||||
var transitioningDelegate: SPStorkTransitioningDelegate?
|
||||
weak var storkDelegate: SPStorkControllerDelegate?
|
||||
|
||||
var pan: UIPanGestureRecognizer?
|
||||
var tap: UITapGestureRecognizer?
|
||||
|
||||
private var closeButton = SPStorkCloseButton()
|
||||
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
|
||||
var afterReleaseDismissing: Bool = false
|
||||
|
||||
private var topSpace: CGFloat {
|
||||
let statusBarHeight: CGFloat = UIApplication.shared.statusBarFrame.height
|
||||
return (statusBarHeight < 25) ? 30 : statusBarHeight
|
||||
}
|
||||
|
||||
private let alpha: CGFloat = 0.51
|
||||
var cornerRadius: CGFloat = 10
|
||||
|
||||
private var scaleForPresentingView: CGFloat {
|
||||
guard let containerView = containerView else { return 0 }
|
||||
let factor = 1 - (self.topSpace * 2 / containerView.frame.height)
|
||||
return factor
|
||||
}
|
||||
|
||||
private var feedbackGenerator: UIImpactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
|
||||
|
||||
override var frameOfPresentedViewInContainerView: CGRect {
|
||||
guard let containerView = containerView else { return .zero }
|
||||
let baseY: CGFloat = self.topSpace + 13
|
||||
let maxHeight: CGFloat = containerView.bounds.height - baseY
|
||||
var height: CGFloat = maxHeight
|
||||
|
||||
if let customHeight = self.customHeight {
|
||||
if customHeight < maxHeight {
|
||||
height = customHeight
|
||||
} else {
|
||||
print("SPStorkController - Custom height change to default value. Your height more maximum value")
|
||||
}
|
||||
}
|
||||
return CGRect(x: 0, y: containerView.bounds.height - height, width: containerView.bounds.width, height: height)
|
||||
}
|
||||
|
||||
override func presentationTransitionWillBegin() {
|
||||
super.presentationTransitionWillBegin()
|
||||
|
||||
if !self.hapticMoments.isEmpty {
|
||||
self.feedbackGenerator.prepare()
|
||||
}
|
||||
|
||||
guard let containerView = self.containerView, let presentedView = self.presentedView, let window = containerView.window else { return }
|
||||
|
||||
if self.showIndicator {
|
||||
self.indicatorView.color = self.indicatorColor
|
||||
let tap = UITapGestureRecognizer.init(target: self, action: #selector(self.dismissAction))
|
||||
tap.cancelsTouchesInView = false
|
||||
self.indicatorView.addGestureRecognizer(tap)
|
||||
presentedView.addSubview(self.indicatorView)
|
||||
}
|
||||
self.updateLayoutIndicator()
|
||||
self.indicatorView.style = .arrow
|
||||
self.gradeView.alpha = 0
|
||||
|
||||
if self.showCloseButton {
|
||||
self.closeButton.addTarget(self, action: #selector(self.dismissAction), for: .touchUpInside)
|
||||
presentedView.addSubview(self.closeButton)
|
||||
}
|
||||
self.updateLayoutCloseButton()
|
||||
|
||||
let initialFrame: CGRect = presentingViewController.isPresentedAsStork ? presentingViewController.view.frame : containerView.bounds
|
||||
|
||||
containerView.insertSubview(self.snapshotViewContainer, belowSubview: presentedViewController.view)
|
||||
self.snapshotViewContainer.frame = initialFrame
|
||||
self.updateSnapshot()
|
||||
self.snapshotView?.layer.cornerRadius = 0
|
||||
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 = 1 - self.alpha
|
||||
snapshotView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotView.contentMode = .top
|
||||
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()
|
||||
})
|
||||
|
||||
if self.hapticMoments.contains(.willPresent) {
|
||||
self.feedbackGenerator.impactOccurred()
|
||||
}
|
||||
}
|
||||
|
||||
override func presentationTransitionDidEnd(_ completed: Bool) {
|
||||
super.presentationTransitionDidEnd(completed)
|
||||
guard let containerView = containerView else { return }
|
||||
self.updateSnapshot()
|
||||
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.tapAroundToDismissEnabled {
|
||||
self.tap = UITapGestureRecognizer.init(target: self, action: #selector(self.dismissAction))
|
||||
self.tap?.cancelsTouchesInView = false
|
||||
self.snapshotViewContainer.addGestureRecognizer(self.tap!)
|
||||
}
|
||||
|
||||
if self.swipeToDismissEnabled {
|
||||
self.pan = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan))
|
||||
self.pan!.delegate = self
|
||||
self.pan!.maximumNumberOfTouches = 1
|
||||
self.pan!.cancelsTouchesInView = false
|
||||
self.presentedViewController.view.addGestureRecognizer(self.pan!)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func dismissAction() {
|
||||
self.presentingViewController.view.endEditing(true)
|
||||
self.presentedViewController.view.endEditing(true)
|
||||
self.presentedViewController.dismiss(animated: true, completion: {
|
||||
self.storkDelegate?.didDismissStorkByTap?()
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
snapshotView.contentMode = .top
|
||||
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(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
|
||||
if self.hapticMoments.contains(.willDismiss) {
|
||||
self.feedbackGenerator.impactOccurred()
|
||||
}
|
||||
}, 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()
|
||||
self.indicatorView.removeFromSuperview()
|
||||
self.closeButton.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(self.pan), self.swipeToDismissEnabled else { return }
|
||||
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
self.workGester = true
|
||||
self.indicatorView.style = .line
|
||||
self.presentingViewController.view.layer.removeAllAnimations()
|
||||
self.presentingViewController.view.endEditing(true)
|
||||
self.presentedViewController.view.endEditing(true)
|
||||
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: containerView)
|
||||
case .changed:
|
||||
self.workGester = true
|
||||
let translation = gestureRecognizer.translation(in: presentedView)
|
||||
if self.swipeToDismissEnabled {
|
||||
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 >= self.translateForDismiss {
|
||||
self.presentedViewController.dismiss(animated: true, completion: {
|
||||
self.storkDelegate?.didDismissStorkBySwipe?()
|
||||
})
|
||||
} 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
|
||||
self.gradeView.alpha = self.alpha
|
||||
})
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if let gester = gestureRecognizer as? UIPanGestureRecognizer {
|
||||
let velocity = gester.velocity(in: self.presentedViewController.view)
|
||||
return abs(velocity.y) > abs(velocity.x)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ translation: CGFloat) {
|
||||
if !self.workGester {
|
||||
self.updatePresentedViewForTranslation(inVerticalDirection: translation)
|
||||
}
|
||||
}
|
||||
|
||||
func updatePresentingController() {
|
||||
if self.startDismissing { return }
|
||||
self.updateSnapshot()
|
||||
}
|
||||
|
||||
func setIndicator(style: SPStorkIndicatorView.Style) {
|
||||
self.indicatorView.style = style
|
||||
}
|
||||
|
||||
func setIndicator(visible: Bool, forse: Bool) {
|
||||
guard self.hideIndicatorWhenScroll else { return }
|
||||
let newAlpha: CGFloat = visible ? 1 : 0
|
||||
if forse {
|
||||
self.indicatorView.removeAllAnimations()
|
||||
self.indicatorView.alpha = newAlpha
|
||||
return
|
||||
}
|
||||
if self.indicatorView.alpha == newAlpha {
|
||||
return
|
||||
}
|
||||
UIView.animate(withDuration: 0.18, animations: {
|
||||
self.indicatorView.alpha = newAlpha
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
let scaleFactor = 1 + (translationForModal / 5000)
|
||||
self.snapshotView?.transform = CGAffineTransform.init(scaleX: scaleFactor, y: scaleFactor)
|
||||
let gradeFactor = 1 + (translationForModal / 7000)
|
||||
self.gradeView.alpha = self.alpha - ((gradeFactor - 1) * 15)
|
||||
} else {
|
||||
self.presentedView?.transform = CGAffineTransform.identity
|
||||
}
|
||||
|
||||
if self.swipeToDismissEnabled {
|
||||
let afterRealseDismissing = (translation >= self.translateForDismiss)
|
||||
if afterRealseDismissing != self.afterReleaseDismissing {
|
||||
self.afterReleaseDismissing = afterRealseDismissing
|
||||
if self.hapticMoments.contains(.willDismissIfRelease) {
|
||||
self.feedbackGenerator.impactOccurred()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
self.updateLayoutCloseButton()
|
||||
}, 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 updateLayoutCloseButton() {
|
||||
guard let presentedView = self.presentedView else { return }
|
||||
self.closeButton.sizeToFit()
|
||||
self.closeButton.layout(bottomX: presentedView.frame.width - 19, y: 19)
|
||||
}
|
||||
|
||||
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.snapshotView?.layer.cornerRadius = self.cornerRadius
|
||||
self.snapshotView?.layer.masksToBounds = true
|
||||
if #available(iOS 11.0, *) {
|
||||
snapshotView?.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -24,14 +24,15 @@ import UIKit
|
||||
final class SPStorkPresentingAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
guard let presentedViewController = transitionContext.viewController(forKey: .to) else {
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
presentedViewController.view.frame = finalFrameForPresentedView
|
||||
presentedViewController.view.frame.origin.y = containerView.bounds.height
|
||||
|
||||
UIView.animate(
|
||||
withDuration: transitionDuration(using: transitionContext),
|
||||
+23
@@ -23,9 +23,32 @@ import UIKit
|
||||
|
||||
public final class SPStorkTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
|
||||
|
||||
public var swipeToDismissEnabled: Bool = true
|
||||
public var tapAroundToDismissEnabled: Bool = true
|
||||
public var showCloseButton: Bool = false
|
||||
public var showIndicator: Bool = true
|
||||
public var indicatorColor: UIColor = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1)
|
||||
public var hideIndicatorWhenScroll: Bool = false
|
||||
public var customHeight: CGFloat? = nil
|
||||
public var translateForDismiss: CGFloat = 200
|
||||
public var cornerRadius: CGFloat = 10
|
||||
public var hapticMoments: [SPStorkHapticMoments] = [.willDismissIfRelease]
|
||||
public weak var storkDelegate: SPStorkControllerDelegate? = nil
|
||||
|
||||
public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
|
||||
let controller = SPStorkPresentationController(presentedViewController: presented, presenting: presenting)
|
||||
controller.swipeToDismissEnabled = self.swipeToDismissEnabled
|
||||
controller.tapAroundToDismissEnabled = self.tapAroundToDismissEnabled
|
||||
controller.showCloseButton = self.showCloseButton
|
||||
controller.showIndicator = self.showIndicator
|
||||
controller.indicatorColor = self.indicatorColor
|
||||
controller.hideIndicatorWhenScroll = self.hideIndicatorWhenScroll
|
||||
controller.customHeight = self.customHeight
|
||||
controller.translateForDismiss = self.translateForDismiss
|
||||
controller.cornerRadius = self.cornerRadius
|
||||
controller.hapticMoments = self.hapticMoments
|
||||
controller.transitioningDelegate = self
|
||||
controller.storkDelegate = self.storkDelegate
|
||||
return controller
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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
|
||||
|
||||
open class SPStorkCloseButton: UIButton {
|
||||
|
||||
let iconView = SPStorkCloseView()
|
||||
|
||||
var widthIconFactor: CGFloat = 1
|
||||
var heightIconFactor: CGFloat = 1
|
||||
|
||||
var color = UIColor.blue {
|
||||
didSet {
|
||||
self.iconView.color = self.color
|
||||
}
|
||||
}
|
||||
|
||||
override open var isHighlighted: Bool {
|
||||
didSet {
|
||||
self.iconView.color = self.color.withAlphaComponent(self.isHighlighted ? 0.7 : 1)
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
self.iconView.isUserInteractionEnabled = false
|
||||
self.addSubview(self.iconView)
|
||||
self.backgroundColor = UIColor.init(red: 239 / 255.0, green: 239 / 255.0, blue: 244 / 255.0, alpha: 1)
|
||||
self.color = UIColor.init(red: 142 / 255.0, green: 142 / 255.0, blue: 147 / 255.0, alpha: 1)
|
||||
self.widthIconFactor = 0.36
|
||||
self.heightIconFactor = 0.36
|
||||
}
|
||||
|
||||
func layout(bottomX: CGFloat, y: CGFloat) {
|
||||
self.sizeToFit()
|
||||
self.frame.origin.x = bottomX - self.frame.width
|
||||
self.frame.origin.y = y
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.layer.cornerRadius = self.frame.width / 2
|
||||
self.iconView.frame = CGRect.init(x: 0, y: 0, width: self.frame.width * self.widthIconFactor, height: self.frame.height * self.heightIconFactor)
|
||||
self.iconView.center = CGPoint.init(x: self.frame.width / 2, y: self.frame.height / 2)
|
||||
}
|
||||
|
||||
override open func sizeToFit() {
|
||||
super.sizeToFit()
|
||||
self.frame = CGRect.init(x: self.frame.origin.x, y: self.frame.origin.y, width: 30, height: 30)
|
||||
}
|
||||
}
|
||||
+14
-17
@@ -21,33 +21,30 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class SPCollectionViewCell: UICollectionViewCell {
|
||||
open class SPStorkCloseView: UIView {
|
||||
|
||||
var currentIndexPath: IndexPath?
|
||||
|
||||
public override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
self.currentIndexPath = nil
|
||||
var color = UIColor.blue {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SPCollectionContainerCell<ContentView: UIView>: UICollectionViewCell {
|
||||
|
||||
let view = ContentView.init()
|
||||
var currentIndexPath: IndexPath?
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
private func commonInit() {
|
||||
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)
|
||||
override open func draw(_ rect: CGRect) {
|
||||
super.draw(rect)
|
||||
SPStorkCodeDraw.drawClose(frame: rect, resizing: .aspectFit, color: self.color)
|
||||
}
|
||||
}
|
||||
|
||||
Regular → Executable
+25
-14
@@ -21,58 +21,69 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPStorkIndicatorView: UIView {
|
||||
open class SPStorkIndicatorView: UIView {
|
||||
|
||||
var style: Style = .line {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var color: UIColor = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1) {
|
||||
didSet {
|
||||
self.leftView.backgroundColor = self.color
|
||||
self.rightView.backgroundColor = self.color
|
||||
}
|
||||
}
|
||||
|
||||
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(hex: "CAC9CF")
|
||||
self.rightView.backgroundColor = UIColor.init(hex: "CAC9CF")
|
||||
self.color = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
override func sizeToFit() {
|
||||
override open 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 {
|
||||
+2
-2
@@ -21,7 +21,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class SPAnimation {
|
||||
enum SPAnimation {
|
||||
|
||||
static func animate(_ duration: TimeInterval,
|
||||
animations: (() -> Void)!,
|
||||
@@ -47,7 +47,7 @@ public class SPAnimation {
|
||||
withComplection completion: (() -> Void)! = {}) {
|
||||
|
||||
var optionsWithRepeatition = options
|
||||
optionsWithRepeatition.insert([.autoreverse, .repeat])
|
||||
optionsWithRepeatition.insert([.autoreverse, .repeat, .allowUserInteraction])
|
||||
|
||||
self.animate(
|
||||
duration,
|
||||
+4
-4
@@ -21,11 +21,11 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class SPAnimationAlpha {
|
||||
class SPAnimationAlpha {
|
||||
|
||||
fileprivate static let durationListAnimation: TimeInterval = 0.45
|
||||
fileprivate static let coefLenthForTransition: CGFloat = 2.8
|
||||
fileprivate static let delayPerItem: TimeInterval = 0.09
|
||||
static let durationListAnimation: TimeInterval = 0.45
|
||||
static let coefLenthForTransition: CGFloat = 2.8
|
||||
static let delayPerItem: TimeInterval = 0.09
|
||||
|
||||
static func hideList(_ duration: TimeInterval = durationListAnimation,
|
||||
views: [UIView],
|
||||
+3
-3
@@ -21,10 +21,10 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class SPAnimationSpring {
|
||||
class SPAnimationSpring {
|
||||
|
||||
fileprivate static let spring: CGFloat = 1
|
||||
fileprivate static let velocity: CGFloat = 1
|
||||
static let spring: CGFloat = 1
|
||||
static let velocity: CGFloat = 1
|
||||
|
||||
static func animate(_ duration: TimeInterval,
|
||||
animations: (() -> Void)!,
|
||||
+4
-4
@@ -21,11 +21,11 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class SPAnimationUpward {
|
||||
class SPAnimationUpward {
|
||||
|
||||
fileprivate static let durationListAnimation: TimeInterval = 0.45
|
||||
fileprivate static let coefLenthForTransition: CGFloat = 2.8
|
||||
fileprivate static let delayPerItem: TimeInterval = 0.09
|
||||
static let durationListAnimation: TimeInterval = 0.45
|
||||
static let coefLenthForTransition: CGFloat = 2.8
|
||||
static let delayPerItem: TimeInterval = 0.09
|
||||
|
||||
static func hide(_ duration: TimeInterval,
|
||||
view: UIView,
|
||||
@@ -0,0 +1,76 @@
|
||||
// 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
|
||||
import StoreKit
|
||||
|
||||
struct SPApp {
|
||||
|
||||
static var udid: String? {
|
||||
return UIDevice.current.identifierForVendor?.uuidString
|
||||
}
|
||||
|
||||
static var displayName: String? {
|
||||
return Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
|
||||
}
|
||||
|
||||
static var rootController: UIViewController? {
|
||||
return UIApplication.shared.keyWindow?.rootViewController
|
||||
}
|
||||
|
||||
static var safeArea: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
return UIApplication.shared.keyWindow?.safeArea ?? UIEdgeInsets.zero
|
||||
} else {
|
||||
return UIEdgeInsets.zero
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
static func set(elementsColor: UIColor) {
|
||||
UINavigationController.elementsColor = elementsColor
|
||||
UIAlertController.elementsColor = elementsColor
|
||||
UITabBarController.elementsColor = elementsColor
|
||||
UITabBar.appearance().tintColor = elementsColor
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
+16
-17
@@ -21,25 +21,24 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct SPLaunch {
|
||||
extension SPApp {
|
||||
|
||||
static func run() {
|
||||
self.count += 1
|
||||
}
|
||||
|
||||
static var count: Int {
|
||||
get {
|
||||
return UserDefaults.standard.value(forKey: "SPLaunchCount") as? Int ?? 0
|
||||
struct Badge {
|
||||
|
||||
static var number: Int {
|
||||
get {
|
||||
return UIApplication.shared.applicationIconBadgeNumber
|
||||
}
|
||||
set {
|
||||
UIApplication.shared.applicationIconBadgeNumber = newValue
|
||||
}
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "SPLaunchCount")
|
||||
|
||||
static func reset() {
|
||||
UIApplication.shared.applicationIconBadgeNumber = 0
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
static var isFirstOpen: Bool {
|
||||
return (self.count == 1) || (self.count == 0)
|
||||
}
|
||||
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
+21
-16
@@ -21,25 +21,30 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPScrollViewController: SPBaseViewController {
|
||||
extension SPApp {
|
||||
|
||||
let scrollView = SPScrollView()
|
||||
|
||||
override func viewDidLoad() {
|
||||
struct Launch {
|
||||
|
||||
self.view.backgroundColor = SPNativeStyleKit.Colors.customGray
|
||||
static func run() {
|
||||
self.count += 1
|
||||
if (UserDefaults.standard.object(forKey: "SPDateFirstLaunch") as? Date) == nil {
|
||||
UserDefaults.standard.set(Date(), forKey: "SPDateFirstLaunch")
|
||||
}
|
||||
}
|
||||
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.showsVerticalScrollIndicator = true
|
||||
self.scrollView.indicatorStyle = .black
|
||||
self.view.addSubview(self.scrollView)
|
||||
static var count: Int {
|
||||
get { return UserDefaults.standard.value(forKey: "SPLaunchCount") as? Int ?? 0 }
|
||||
set { UserDefaults.standard.set(newValue, forKey: "SPLaunchCount") }
|
||||
}
|
||||
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
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)
|
||||
static var isFirstLaunch: Bool {
|
||||
return (self.count == 1) || (self.count == 0)
|
||||
}
|
||||
|
||||
static var dateFirstLaunch: Date {
|
||||
return ((UserDefaults.standard.object(forKey: "SPDateFirstLaunch") as? Date) ?? Date())
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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
|
||||
import SafariServices
|
||||
|
||||
extension SPApp {
|
||||
|
||||
static func open(app: SPSystemApp) {
|
||||
switch app {
|
||||
case SPSystemApp.photos:
|
||||
guard let settingsUrl = URL(string: "photos-redirect://") else {
|
||||
return
|
||||
}
|
||||
|
||||
if UIApplication.shared.canOpenURL(settingsUrl) {
|
||||
if #available(iOS 10.0, *) {
|
||||
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
|
||||
print("SPApp - Photos opened: \(success)")
|
||||
})
|
||||
} else {
|
||||
UIApplication.shared.openURL(settingsUrl as URL)
|
||||
}
|
||||
} else {
|
||||
print("SPApp - Photos not opened")
|
||||
}
|
||||
case SPSystemApp.setting:
|
||||
DispatchQueue.main.async {
|
||||
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
|
||||
return
|
||||
}
|
||||
|
||||
if UIApplication.shared.canOpenURL(settingsUrl) {
|
||||
if #available(iOS 10.0, *) {
|
||||
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
|
||||
print("SPApp - Settings opened: \(success)")
|
||||
})
|
||||
} else {
|
||||
UIApplication.shared.openURL(settingsUrl as URL)
|
||||
}
|
||||
} else {
|
||||
print("SPApp - Settings not opened")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func open(link: String, redirect: Bool) {
|
||||
|
||||
guard let url = URL(string: link) else {
|
||||
print("SPOpener - can not create URL")
|
||||
return
|
||||
}
|
||||
|
||||
if redirect {
|
||||
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
|
||||
} else {
|
||||
if let rootController = SPApp.rootController {
|
||||
let safariController = SFSafariViewController.init(url: url)
|
||||
rootController.present(safariController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
|
||||
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
|
||||
}
|
||||
+8
-3
@@ -28,8 +28,8 @@ struct SPAppStore {
|
||||
return "https://itunes.apple.com/by/app/id" + appID
|
||||
}
|
||||
|
||||
static func open(appID: String) {
|
||||
if let url = URL(string: "itms-apps://itunes.apple.com/app/id\(appID)"),
|
||||
static func open(app id: String) {
|
||||
if let url = URL(string: "itms-apps://itunes.apple.com/app/id\(id)"),
|
||||
UIApplication.shared.canOpenURL(url) {
|
||||
if #available(iOS 10.0, *) {
|
||||
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
|
||||
@@ -56,6 +56,12 @@ struct SPAppStore {
|
||||
}
|
||||
}
|
||||
|
||||
static func requestReview() {
|
||||
if #available(iOS 10.3, *) {
|
||||
SKStoreReviewController.requestReview()
|
||||
}
|
||||
}
|
||||
|
||||
static func isUpdateAvailable(completion: @escaping (Bool)->()) {
|
||||
|
||||
guard let info = Bundle.main.infoDictionary,
|
||||
@@ -128,7 +134,6 @@ extension String {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function inserted by Swift 4.2 migrator.
|
||||
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
|
||||
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
|
||||
}
|
||||
+2
-3
@@ -22,21 +22,20 @@
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
public struct SPAudio {
|
||||
struct SPAudio {
|
||||
|
||||
static func notStopBackgroundMusic() {
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category(rawValue: convertFromAVAudioSessionCategory(AVAudioSession.Category.ambient)), mode: AVAudioSession.Mode.default)
|
||||
try AVAudioSession.sharedInstance().setActive(true)
|
||||
} catch {
|
||||
|
||||
print("SPAudio - notStopBackgroundMusic, error")
|
||||
}
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
// Helper function inserted by Swift 4.2 migrator.
|
||||
fileprivate func convertFromAVAudioSessionCategory(_ input: AVAudioSession.Category) -> String {
|
||||
return input.rawValue
|
||||
}
|
||||
+2
-2
@@ -22,7 +22,7 @@
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
public class SPAudioPlayer: NSObject, AVAudioPlayerDelegate {
|
||||
class SPAudioPlayer: NSObject, AVAudioPlayerDelegate {
|
||||
|
||||
fileprivate var player: AVAudioPlayer = AVAudioPlayer()
|
||||
fileprivate var endPlayingComplection: (()->())? = nil
|
||||
@@ -51,7 +51,7 @@ public class SPAudioPlayer: NSObject, AVAudioPlayerDelegate {
|
||||
player.stop()
|
||||
}
|
||||
|
||||
public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
|
||||
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
|
||||
self.endPlayingComplection?()
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -23,9 +23,9 @@ import UIKit
|
||||
|
||||
extension SPCodeDraw {
|
||||
|
||||
public class AudioIconPack : NSObject {
|
||||
class AudioIconPack : NSObject {
|
||||
|
||||
@objc dynamic public class func drawPlay(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawPlay(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -53,7 +53,7 @@ extension SPCodeDraw {
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawPause(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawPause(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -90,7 +90,7 @@ extension SPCodeDraw {
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawStop(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawStop(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -118,13 +118,13 @@ extension SPCodeDraw {
|
||||
}
|
||||
|
||||
@objc(StyleKitNameResizingBehavior)
|
||||
public enum ResizingBehavior: Int {
|
||||
enum ResizingBehavior: Int {
|
||||
case aspectFit /// The content is proportionally resized to fit into the target rectangle.
|
||||
case aspectFill /// The content is proportionally resized to completely fill the target rectangle.
|
||||
case stretch /// The content is stretched to match the entire target rectangle.
|
||||
case center /// The content is centered in the target rectangle, but it is NOT resized.
|
||||
|
||||
public func apply(rect: CGRect, target: CGRect) -> CGRect {
|
||||
func apply(rect: CGRect, target: CGRect) -> CGRect {
|
||||
if rect == target || target == CGRect.zero {
|
||||
return rect
|
||||
}
|
||||
+1
-1
@@ -21,4 +21,4 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct SPCodeDraw { private init(){} }
|
||||
struct SPCodeDraw { private init() {} }
|
||||
+9
-9
@@ -23,9 +23,9 @@ import UIKit
|
||||
|
||||
extension SPCodeDraw {
|
||||
|
||||
public class SocialIconPack : NSObject {
|
||||
class SocialIconPack : NSObject {
|
||||
|
||||
@objc dynamic public class func drawInstagram(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawInstagram(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -115,7 +115,7 @@ extension SPCodeDraw {
|
||||
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawVK(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawVK(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
//// General Declarations
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -167,7 +167,7 @@ extension SPCodeDraw {
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawWhatsapp(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawWhatsapp(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
//// General Declarations
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -237,7 +237,7 @@ extension SPCodeDraw {
|
||||
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawTelegram(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawTelegram(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
//// General Declarations
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -313,7 +313,7 @@ extension SPCodeDraw {
|
||||
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawFacebook(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 41, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawFacebook(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 41, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
//// General Declarations
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -352,7 +352,7 @@ extension SPCodeDraw {
|
||||
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawViber(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 41, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawViber(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 41, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
//// General Declarations
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -443,13 +443,13 @@ extension SPCodeDraw {
|
||||
}
|
||||
|
||||
@objc(SocialIconStyleKitResizingBehavior)
|
||||
public enum ResizingBehavior: Int {
|
||||
enum ResizingBehavior: Int {
|
||||
case aspectFit /// The content is proportionally resized to fit into the target rectangle.
|
||||
case aspectFill /// The content is proportionally resized to completely fill the target rectangle.
|
||||
case stretch /// The content is stretched to match the entire target rectangle.
|
||||
case center /// The content is centered in the target rectangle, but it is NOT resized.
|
||||
|
||||
public func apply(rect: CGRect, target: CGRect) -> CGRect {
|
||||
func apply(rect: CGRect, target: CGRect) -> CGRect {
|
||||
if rect == target || target == CGRect.zero {
|
||||
return rect
|
||||
}
|
||||
+8
-8
@@ -23,15 +23,15 @@ import UIKit
|
||||
|
||||
extension SPCodeDraw {
|
||||
|
||||
public class SystemIconPack : NSObject {
|
||||
class SystemIconPack : NSObject {
|
||||
|
||||
private struct Cache {
|
||||
static let gradient: CGGradient = CGGradient(colorsSpace: nil, colors: [UIColor.red.cgColor, UIColor.red.cgColor] as CFArray, locations: [0, 1])!
|
||||
}
|
||||
|
||||
@objc dynamic public class var gradient: CGGradient { return Cache.gradient }
|
||||
@objc dynamic class var gradient: CGGradient { return Cache.gradient }
|
||||
|
||||
@objc dynamic public class func drawFavorite(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.478, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawFavorite(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.478, blue: 1.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -80,7 +80,7 @@ extension SPCodeDraw {
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawFavoriteFill(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.478, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawFavoriteFill(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.478, blue: 1.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -113,7 +113,7 @@ extension SPCodeDraw {
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawShare(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawShare(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -154,7 +154,7 @@ extension SPCodeDraw {
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawClose(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawClose(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -192,13 +192,13 @@ extension SPCodeDraw {
|
||||
}
|
||||
|
||||
@objc(StyleKitNameResizingBehavior)
|
||||
public enum ResizingBehavior: Int {
|
||||
enum ResizingBehavior: Int {
|
||||
case aspectFit /// The content is proportionally resized to fit into the target rectangle.
|
||||
case aspectFill /// The content is proportionally resized to completely fill the target rectangle.
|
||||
case stretch /// The content is stretched to match the entire target rectangle.
|
||||
case center /// The content is centered in the target rectangle, but it is NOT resized.
|
||||
|
||||
public func apply(rect: CGRect, target: CGRect) -> CGRect {
|
||||
func apply(rect: CGRect, target: CGRect) -> CGRect {
|
||||
if rect == target || target == CGRect.zero {
|
||||
return rect
|
||||
}
|
||||
+985
-119
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
// 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
|
||||
|
||||
struct SPConstraints {
|
||||
|
||||
static func setEqualSizeSuperview(for view: UIView) {
|
||||
if let superView = view.superview {
|
||||
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 init() {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public func delay(_ delay:Double, closure:@escaping ()->()) {
|
||||
func delay(_ delay:Double, closure: @escaping ()->()) {
|
||||
let when = DispatchTime.now() + delay
|
||||
DispatchQueue.main.asyncAfter(deadline: when) {
|
||||
closure()
|
||||
+4
-4
@@ -23,12 +23,12 @@ import UIKit
|
||||
|
||||
struct SPDevice {
|
||||
|
||||
static var isIphone: Bool {
|
||||
return UIDevice.current.isIphone
|
||||
static var iphone: Bool {
|
||||
return UIDevice.current.userInterfaceIdiom == .phone
|
||||
}
|
||||
|
||||
static var isIpad: Bool {
|
||||
return UIDevice.current.isIpad
|
||||
static var ipad: Bool {
|
||||
return UIDevice.current.userInterfaceIdiom == .pad
|
||||
}
|
||||
|
||||
struct Orientation {
|
||||
+3
@@ -30,6 +30,7 @@ struct SPDownloader {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
URLSession.shared.dataTask(with: url) { (data, response, error) in
|
||||
guard
|
||||
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
|
||||
@@ -47,6 +48,8 @@ struct SPDownloader {
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
|
||||
+4
-11
@@ -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.firstIndex(of: item), index + 1 < self.count { return self[index + 1] }
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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
|
||||
|
||||
extension CGRect {
|
||||
|
||||
var bottomX: CGFloat {
|
||||
get { return self.origin.x + self.width }
|
||||
set { self.origin.x = newValue - self.width }
|
||||
}
|
||||
|
||||
var bottomY: CGFloat {
|
||||
get { return self.origin.y + self.height }
|
||||
set { self.origin.y = newValue - self.height }
|
||||
}
|
||||
|
||||
var minSide: CGFloat {
|
||||
return min(self.width, self.height)
|
||||
}
|
||||
|
||||
mutating func set(width: CGFloat) {
|
||||
self = CGRect.init(x: self.origin.x, y: self.origin.y, width: width, height: self.height)
|
||||
}
|
||||
|
||||
mutating func set(height: CGFloat) {
|
||||
self = CGRect.init(x: self.origin.x, y: self.origin.y, width: self.width, height: height)
|
||||
}
|
||||
|
||||
mutating func set(width: CGFloat, height: CGFloat) {
|
||||
self = CGRect.init(x: self.origin.x, y: self.origin.y, width: width, height: height)
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -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)
|
||||
}
|
||||
}
|
||||
+13
-3
@@ -23,10 +23,20 @@ import Foundation
|
||||
|
||||
extension Date {
|
||||
|
||||
func formatted(as dateFormat: String) -> String {
|
||||
|
||||
func format(mask: String) -> String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = dateFormat
|
||||
dateFormatter.dateFormat = mask
|
||||
return dateFormatter.string(from: self)
|
||||
}
|
||||
|
||||
static func create(from value: String) -> Date? {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "dd.MM.yyyy HH:mm"
|
||||
let date = formatter.date(from: value)
|
||||
return date
|
||||
}
|
||||
|
||||
func add(days: Int) -> Date {
|
||||
return Calendar.current.date(byAdding: .day, value: days, to: self) ?? self
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -23,13 +23,13 @@ import Foundation
|
||||
|
||||
extension Strideable {
|
||||
|
||||
public mutating func setIfMore(when value: Self) {
|
||||
mutating func setIfMore(when value: Self) {
|
||||
if self > value {
|
||||
self = value
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func setIfFewer(when value: Self) {
|
||||
mutating func setIfFewer(when value: Self) {
|
||||
if self < value {
|
||||
self = value
|
||||
}
|
||||
+27
-12
@@ -24,6 +24,10 @@ import UIKit
|
||||
|
||||
extension String {
|
||||
|
||||
var digits: String {
|
||||
return components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
|
||||
}
|
||||
|
||||
mutating func dropLast(substring: String) {
|
||||
if self.hasSuffix(substring) {
|
||||
self = String(dropLast(substring.count))
|
||||
@@ -66,21 +70,32 @@ 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
|
||||
}
|
||||
}
|
||||
var isEmptyContent: Bool {
|
||||
return (self.removeAllSpaces() == "")
|
||||
}
|
||||
|
||||
var words: [String] {
|
||||
return components(separatedBy: .punctuationCharacters).joined().components(separatedBy: .whitespaces)
|
||||
}
|
||||
|
||||
func removePrefix(_ prefix: String) -> String {
|
||||
guard self.hasPrefix(prefix) else { return self }
|
||||
return String(self.dropFirst(prefix.count))
|
||||
}
|
||||
|
||||
mutating func replace(_ replacingString: String, with newString: String) {
|
||||
self = self.replacingOccurrences(of: replacingString, with: newString)
|
||||
}
|
||||
|
||||
func replace(_ replacingString: String, with newString: String) -> String {
|
||||
return self.replacingOccurrences(of: replacingString, with: newString)
|
||||
}
|
||||
|
||||
func slice(from: String, to: String) -> String? {
|
||||
return (range(of: from)?.upperBound).flatMap { substringFrom in
|
||||
(range(of: to, range: substringFrom..<endIndex)?.lowerBound).map { substringTo in
|
||||
String(self[substringFrom..<substringTo])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-15
@@ -32,20 +32,19 @@ extension UIAlertController {
|
||||
}
|
||||
}
|
||||
|
||||
public static func show(title: String, message: String, buttonTitle: String, cancelButtonTitle: String? = nil, complection: @escaping ()->() = {}, on viewController: UIViewController) {
|
||||
static func show(title: String, message: String, buttonTitle: String, cancelButtonTitle: String? = nil, complection: @escaping ()->() = {}, on viewController: UIViewController) {
|
||||
let ac = UIAlertController(
|
||||
title: title,
|
||||
message: message,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
|
||||
if cancelButtonTitle != nil {
|
||||
ac.addAction(UIAlertAction.init(
|
||||
title: cancelButtonTitle!,
|
||||
style: UIAlertAction.Style.cancel,
|
||||
handler: nil)
|
||||
)
|
||||
}
|
||||
guard cancelButtonTitle != nil else { return }
|
||||
ac.addAction(UIAlertAction.init(
|
||||
title: cancelButtonTitle!,
|
||||
style: UIAlertAction.Style.cancel,
|
||||
handler: nil)
|
||||
)
|
||||
|
||||
ac.addAction(UIAlertAction.init(
|
||||
title: buttonTitle,
|
||||
@@ -57,17 +56,15 @@ extension UIAlertController {
|
||||
viewController.present(ac, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
public static func сonfirm(title: String? = nil, message: String, buttonTitle: String, cancelButtonTitle: String, isDestructive: Bool = false, complection: @escaping (Bool)->(), on viewController: UIViewController) {
|
||||
static func сonfirm(title: String? = nil, message: String, buttonTitle: String, cancelButtonTitle: String, isDestructive: Bool = false, complection: @escaping (Bool)->(), on viewController: UIViewController) {
|
||||
let ac = UIAlertController(
|
||||
title: title,
|
||||
message: message,
|
||||
preferredStyle: .actionSheet
|
||||
)
|
||||
|
||||
var style = UIAlertAction.Style.default
|
||||
if isDestructive {
|
||||
style = .destructive
|
||||
}
|
||||
let style = isDestructive ? .destructive : UIAlertAction.Style.default
|
||||
|
||||
ac.addAction(UIAlertAction.init(
|
||||
title: buttonTitle,
|
||||
style: style,
|
||||
@@ -76,11 +73,10 @@ extension UIAlertController {
|
||||
}))
|
||||
ac.addAction(UIAlertAction.init(
|
||||
title: cancelButtonTitle,
|
||||
style: UIAlertAction.Style.default,
|
||||
style: UIAlertAction.Style.cancel,
|
||||
handler: { (action) in
|
||||
complection(false)
|
||||
}))
|
||||
|
||||
viewController.present(ac, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
@@ -100,4 +96,11 @@ extension UIAlertController {
|
||||
}
|
||||
self.addAction(action)
|
||||
}
|
||||
|
||||
func addCancelAction(title: String, complection: @escaping ()->() = {}) {
|
||||
let action = UIAlertAction(title: title, style: .cancel) { (action) in
|
||||
complection()
|
||||
}
|
||||
self.addAction(action)
|
||||
}
|
||||
}
|
||||
+20
-26
@@ -60,12 +60,21 @@ extension UIButton {
|
||||
|
||||
extension UIButton {
|
||||
|
||||
func setTitle(_ title: String) {
|
||||
func setTitle(_ title: String, color: UIColor? = nil) {
|
||||
self.setTitle(title, for: .normal)
|
||||
if let color = color {
|
||||
self.setTitleColor(color)
|
||||
}
|
||||
}
|
||||
|
||||
func setTitleColor(_ color: UIColor) {
|
||||
self.setTitleColorForNoramlAndHightlightedStates(color: color)
|
||||
self.setTitleColor(color, for: .normal)
|
||||
self.setTitleColor(color.withAlphaComponent(0.7), for: .highlighted)
|
||||
}
|
||||
|
||||
func setImage(_ image: UIImage) {
|
||||
self.setImage(image, for: .normal)
|
||||
self.imageView?.contentMode = .scaleAspectFit
|
||||
}
|
||||
|
||||
func removeAllTargets() {
|
||||
@@ -74,23 +83,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,14 +106,11 @@ extension UIButton {
|
||||
}
|
||||
|
||||
func setAnimatableText(_ text: String, withComplection completion: (() -> Void)! = {}) {
|
||||
|
||||
SPAnimation.animate(0.2,
|
||||
animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
SPAnimation.animate(0.3, animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
}, withComplection: {
|
||||
self.setTitle(text, for: .normal)
|
||||
|
||||
SPAnimation.animate(0.2, animations: {
|
||||
SPAnimation.animate(0.3, animations: {
|
||||
self.titleLabel?.alpha = 1
|
||||
}, withComplection: {
|
||||
completion()
|
||||
@@ -118,25 +119,18 @@ extension UIButton {
|
||||
}
|
||||
|
||||
func hideContent(completion: (() -> Void)! = {}) {
|
||||
SPAnimation.animate(0.2,
|
||||
animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
SPAnimation.animate(0.25, animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
}, withComplection: {
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
func showContent(completion: (() -> Void)! = {}) {
|
||||
SPAnimation.animate(0.2,
|
||||
animations: {
|
||||
self.titleLabel?.alpha = 1
|
||||
SPAnimation.animate(0.25, animations: {
|
||||
self.titleLabel?.alpha = 1
|
||||
}, withComplection: {
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
func setTitleColorForNoramlAndHightlightedStates(color: UIColor) {
|
||||
self.setTitleColor(color, for: .normal)
|
||||
self.setTitleColor(color.withAlphaComponent(0.7), for: .highlighted)
|
||||
}
|
||||
}
|
||||
+24
@@ -29,4 +29,28 @@ extension UICollectionView {
|
||||
let visibleIndexPath = self.indexPathForItem(at: visiblePoint)
|
||||
return visibleIndexPath
|
||||
}
|
||||
|
||||
func lastRow(indexPath: IndexPath) -> Int {
|
||||
return self.numberOfItems(inSection: indexPath.section) - 1
|
||||
}
|
||||
|
||||
func isLastRow(indexPath: IndexPath) -> Bool {
|
||||
return indexPath.row == self.lastRow(indexPath: indexPath)
|
||||
}
|
||||
|
||||
func reloadData(animated: Bool, complection: @escaping ()->() = {}) {
|
||||
if animated {
|
||||
UIView.transition(
|
||||
with: self,
|
||||
duration: 0.3,
|
||||
options: [.transitionCrossDissolve, UIView.AnimationOptions.beginFromCurrentState],
|
||||
animations: {
|
||||
self.reloadData()
|
||||
}, completion: {(state) in
|
||||
complection()
|
||||
})
|
||||
} else {
|
||||
self.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UIColor {
|
||||
extension UIColor {
|
||||
|
||||
convenience init(hex: String) {
|
||||
var red: CGFloat = 0.0
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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
|
||||
|
||||
extension UIFont {
|
||||
|
||||
static func system(weight: FontWeight, size: CGFloat) -> UIFont {
|
||||
return UIFont.systemFont(ofSize: size, weight: self.weight(for: weight))
|
||||
}
|
||||
|
||||
private static func weight(for weight: FontWeight) -> UIFont.Weight {
|
||||
switch weight {
|
||||
case .ultraLight:
|
||||
return UIFont.Weight.ultraLight
|
||||
case .light:
|
||||
return UIFont.Weight.light
|
||||
case .medium:
|
||||
return UIFont.Weight.medium
|
||||
case .regular:
|
||||
return UIFont.Weight.regular
|
||||
case .bold:
|
||||
return UIFont.Weight.bold
|
||||
case .demiBold:
|
||||
return UIFont.Weight.semibold
|
||||
case .heavy:
|
||||
return UIFont.Weight.heavy
|
||||
default:
|
||||
return UIFont.Weight.regular
|
||||
}
|
||||
}
|
||||
|
||||
enum FontWeight {
|
||||
case regular
|
||||
case medium
|
||||
case light
|
||||
case ultraLight
|
||||
case heavy
|
||||
case bold
|
||||
case demiBold
|
||||
case none
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIImage {
|
||||
|
||||
func resize(width: CGFloat) -> UIImage {
|
||||
let scale = width / self.size.width
|
||||
let newHeight = self.size.height * scale
|
||||
UIGraphicsBeginImageContext(CGSize(width: width, height: newHeight))
|
||||
self.draw(in: CGRect(x: 0, y: 0, width: width, height: newHeight))
|
||||
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return newImage!
|
||||
}
|
||||
|
||||
var alwaysOriginal: UIImage {
|
||||
return self.withRenderingMode(.alwaysOriginal)
|
||||
}
|
||||
|
||||
var alwaysTemplate: UIImage {
|
||||
return self.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
}
|
||||
+4
-9
@@ -23,18 +23,13 @@ import UIKit
|
||||
|
||||
extension UIImageView {
|
||||
|
||||
public func setNativeStyle() {
|
||||
func setNative() {
|
||||
self.layer.borderWidth = 0.5
|
||||
self.layer.borderColor = SPNativeStyleKit.Colors.midGray.cgColor
|
||||
self.layer.borderColor = SPNativeColors.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 }) {
|
||||
func downloadedFrom(url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit, withComplection complection: @escaping (UIImage?) -> () = {_ in }) {
|
||||
DispatchQueue.main.async {
|
||||
self.contentMode = mode
|
||||
}
|
||||
@@ -52,7 +47,7 @@ extension UIImageView {
|
||||
}.resume()
|
||||
}
|
||||
|
||||
public func downloadedFrom(link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit, withComplection complection: @escaping (UIImage?) -> () = {_ in }) {
|
||||
func downloadedFrom(link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit, withComplection complection: @escaping (UIImage?) -> () = {_ in }) {
|
||||
guard let url = URL(string: link) else { return }
|
||||
downloadedFrom(url: url, contentMode: mode, withComplection: complection)
|
||||
}
|
||||
+39
-2
@@ -21,7 +21,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UILabel {
|
||||
extension UILabel {
|
||||
|
||||
func setShadowOffsetForLetters(blurRadius: CGFloat = 0, widthOffset: CGFloat = 0, heightOffset: CGFloat, opacity: CGFloat) {
|
||||
self.layer.shadowRadius = blurRadius
|
||||
@@ -42,7 +42,7 @@ public extension UILabel {
|
||||
self.setShadowOffsetForLetters(blurRadius: 0, widthOffset: 0, heightOffset: 0, opacity: 0)
|
||||
}
|
||||
|
||||
func setCenteringAlignment() {
|
||||
func setCenterAlignment() {
|
||||
self.textAlignment = .center
|
||||
self.baselineAdjustment = .alignCenters
|
||||
}
|
||||
@@ -53,4 +53,41 @@ public extension UILabel {
|
||||
attributedText = NSAttributedString(string: textString, attributes: attrs)
|
||||
}
|
||||
}
|
||||
|
||||
func setLineSpacing(_ lineSpacing: CGFloat = 0.0, lineHeightMultiple: CGFloat = 0.0) {
|
||||
|
||||
guard let labelText = self.text else { return }
|
||||
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
paragraphStyle.lineSpacing = lineSpacing
|
||||
paragraphStyle.lineHeightMultiple = lineHeightMultiple
|
||||
|
||||
let attributedString:NSMutableAttributedString
|
||||
if let labelattributedText = self.attributedText {
|
||||
attributedString = NSMutableAttributedString(attributedString: labelattributedText)
|
||||
} else {
|
||||
attributedString = NSMutableAttributedString(string: labelText)
|
||||
}
|
||||
|
||||
attributedString.addAttribute(NSAttributedString.Key.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
|
||||
|
||||
self.attributedText = attributedString
|
||||
}
|
||||
|
||||
func setFormat(text: String, positions: [(start: Int, length: Int)], hithiglightFont: UIFont, higlightColor: UIColor) {
|
||||
|
||||
let title = NSMutableAttributedString.init(string: text)
|
||||
|
||||
for position in positions {
|
||||
title.addAttributes(
|
||||
[
|
||||
NSAttributedString.Key.foregroundColor : higlightColor,
|
||||
NSAttributedString.Key.font : hithiglightFont
|
||||
],
|
||||
range: NSRange.init(location: position.start, length: position.length)
|
||||
)
|
||||
}
|
||||
|
||||
self.attributedText = title
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -25,7 +25,11 @@ extension UINavigationController {
|
||||
|
||||
static var elementsColor: UIColor {
|
||||
get {
|
||||
return UINavigationBar.appearance().tintColor
|
||||
if UINavigationBar.appearance().tintColor != nil {
|
||||
return UINavigationBar.appearance().tintColor
|
||||
} else {
|
||||
return SPNativeColors.blue
|
||||
}
|
||||
}
|
||||
set {
|
||||
UINavigationBar.appearance().tintColor = newValue
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
// 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
|
||||
|
||||
extension UITabBarController {
|
||||
|
||||
static var elementsColor: UIColor {
|
||||
get {
|
||||
if UITabBar.appearance().tintColor != nil {
|
||||
return UITabBar.appearance().tintColor
|
||||
} else {
|
||||
return SPNativeColors.blue
|
||||
}
|
||||
}
|
||||
set {
|
||||
UINavigationBar.appearance().tintColor = newValue
|
||||
}
|
||||
}
|
||||
|
||||
func addTabBarItem(title: String, image: UIImage, selectedImage: UIImage? = nil, controller: UIViewController) {
|
||||
|
||||
let tabBarItem = UITabBarItem(
|
||||
title: title,
|
||||
image: image,
|
||||
selectedImage: selectedImage ?? image
|
||||
)
|
||||
|
||||
controller.tabBarItem = tabBarItem
|
||||
|
||||
if self.viewControllers == nil { self.viewControllers = [controller] }
|
||||
else { self.viewControllers?.append(controller) }
|
||||
}
|
||||
}
|
||||
+6
-32
@@ -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
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -23,7 +23,7 @@ import UIKit
|
||||
|
||||
extension UITableViewCell {
|
||||
|
||||
public var accessoryView: UIView? {
|
||||
var accessoryView: UIView? {
|
||||
return subviews.compactMap { $0 as? UIButton }.first
|
||||
}
|
||||
|
||||
@@ -33,12 +33,12 @@ extension UITableViewCell {
|
||||
}
|
||||
set {
|
||||
let backgroundView = UIView()
|
||||
backgroundView.backgroundColor = SPNativeStyleKit.Colors.customGray
|
||||
backgroundView.backgroundColor = SPNativeColors.customGray
|
||||
self.selectedBackgroundView = backgroundView
|
||||
}
|
||||
}
|
||||
|
||||
public func highlight() {
|
||||
func highlight() {
|
||||
self.setHighlighted(true, animated: false)
|
||||
self.setHighlighted(false, animated: true)
|
||||
}
|
||||
+19
-15
@@ -39,7 +39,6 @@ extension UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Keyboard
|
||||
extension UIViewController {
|
||||
|
||||
func dismissKeyboardWhenTappedAround() {
|
||||
@@ -53,16 +52,13 @@ extension UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Add image to Library
|
||||
extension UIViewController {
|
||||
|
||||
func save(image: UIImage) {
|
||||
if SPPermission.isAllow(.photoLibrary) {
|
||||
if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized {
|
||||
UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)
|
||||
} else {
|
||||
SPPermission.request(.photoLibrary) {
|
||||
UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)
|
||||
}
|
||||
print("Saving image error. Not allowed permission")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +103,6 @@ extension UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Navigation Bar
|
||||
extension UIViewController {
|
||||
|
||||
func setPrefersLargeNavigationTitle(_ title: String, smallScreenToSmallBar: Bool = true) {
|
||||
@@ -129,31 +124,40 @@ extension UIViewController {
|
||||
switch style {
|
||||
case .large:
|
||||
if #available(iOS 11.0, *) {
|
||||
self.navigationController?.navigationBar.prefersLargeTitles = true
|
||||
self.navigationItem.largeTitleDisplayMode = .always
|
||||
}
|
||||
case .small:
|
||||
if #available(iOS 11.0, *) {
|
||||
self.navigationItem.largeTitleDisplayMode = .never
|
||||
}
|
||||
}
|
||||
case .stork:
|
||||
if #available(iOS 11.0, *) {
|
||||
self.navigationItem.largeTitleDisplayMode = .never
|
||||
}
|
||||
case .noContent:
|
||||
if #available(iOS 11.0, *) {
|
||||
self.navigationItem.largeTitleDisplayMode = .never
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
|
||||
var topSafeArea: CGFloat {
|
||||
return self.view.topSafeArea
|
||||
}
|
||||
|
||||
var bottomSafeArea: CGFloat {
|
||||
return self.view.bottomSafeArea
|
||||
var safeArea: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
return self.view.safeAreaInsets
|
||||
} else {
|
||||
return UIEdgeInsets.zero
|
||||
}
|
||||
}
|
||||
|
||||
var navigationBarHeight: CGFloat {
|
||||
return self.navigationController?.navigationBar.frame.height ?? 0
|
||||
}
|
||||
|
||||
var statusBarHeight: CGFloat {
|
||||
static var statusBarHeight: CGFloat {
|
||||
return UIApplication.shared.statusBarFrame.height
|
||||
}
|
||||
}
|
||||
+43
-68
@@ -21,49 +21,38 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
// MARK: - layout
|
||||
public extension UIView {
|
||||
extension UIView {
|
||||
|
||||
var topSafeArea: CGFloat {
|
||||
var topSafeArea: CGFloat = 0
|
||||
if #available(iOS 11.0, *) {
|
||||
topSafeArea = self.safeAreaInsets.top
|
||||
var controller: UIViewController? {
|
||||
get {
|
||||
if let nextResponder = self.next as? UIViewController { return nextResponder }
|
||||
else if let nextResponder = self.next as? UIView { return nextResponder.controller }
|
||||
else { return nil }
|
||||
}
|
||||
return topSafeArea
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
|
||||
var bottomSafeArea: CGFloat {
|
||||
var bottomSafeArea: CGFloat = 0
|
||||
var safeArea: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
bottomSafeArea = self.safeAreaInsets.bottom
|
||||
return self.safeAreaInsets
|
||||
} else{
|
||||
return UIEdgeInsets.zero
|
||||
}
|
||||
return bottomSafeArea
|
||||
}
|
||||
|
||||
func setHeight(_ height: CGFloat) {
|
||||
self.frame = CGRect.init(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.width, height: height)
|
||||
func setBounds(_ view: UIView, withWidthFactor widthFactor: CGFloat = 1, maxWidth: CGFloat? = nil, withHeightFactor heightFactor: CGFloat = 1, maxHeight: CGFloat? = nil, withCentering: Bool = false) {
|
||||
self.setBounds(view.bounds, withWidthFactor: widthFactor, maxWidth: maxWidth, withHeightFactor: heightFactor, maxHeight: maxHeight, withCentering: withCentering)
|
||||
}
|
||||
|
||||
func setWidth(_ width: CGFloat) {
|
||||
self.frame = CGRect.init(x: self.frame.origin.x, y: self.frame.origin.y, width: width, height: self.frame.height)
|
||||
}
|
||||
|
||||
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) {
|
||||
func setBounds(_ 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)
|
||||
|
||||
@@ -73,28 +62,20 @@ public extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
func setEqualsBoundsFromSuperview(customWidth: CGFloat? = nil, customHeight: CGFloat? = nil) {
|
||||
|
||||
if self.superview == nil {
|
||||
return
|
||||
}
|
||||
|
||||
func setSuperviewBounds(customWidth: CGFloat? = nil, customHeight: CGFloat? = nil) {
|
||||
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))
|
||||
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: customWidth!, height: self.superview!.frame.height))
|
||||
}
|
||||
|
||||
if customHeight != nil {
|
||||
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: self.frame.width, height: customHeight!))
|
||||
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: self.superview!.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 +84,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,19 +95,23 @@ public extension UIView {
|
||||
)
|
||||
}
|
||||
|
||||
func setXCenteringFromSuperview() {
|
||||
func setYCenter() {
|
||||
self.center.y = (self.superview?.frame.height ?? 0) / 2
|
||||
}
|
||||
|
||||
func setXCenter() {
|
||||
self.center.x = (self.superview?.frame.width ?? 0) / 2
|
||||
}
|
||||
|
||||
func setToCenterInSuperview() {
|
||||
func setToCenter() {
|
||||
self.center = CGPoint.init(x: ((self.superview?.frame.width) ?? 0) / 2, y: ((self.superview?.frame.height) ?? 0) / 2)
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIView {
|
||||
extension UIView {
|
||||
|
||||
func setParalax(amountFactor: CGFloat) {
|
||||
let amount = self.frame.minSideSize * amountFactor
|
||||
let amount = self.frame.minSide * amountFactor
|
||||
self.setParalax(amount: amount)
|
||||
}
|
||||
|
||||
@@ -148,29 +131,19 @@ public extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - convertToImage
|
||||
public extension UIView {
|
||||
|
||||
func convertToImage() -> UIImage {
|
||||
return UIImage.drawFromView(view: self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - gradeView
|
||||
public extension UIView {
|
||||
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.setEqualSizeSuperview(for: gradeView)
|
||||
gradeView.alpha = alpha
|
||||
gradeView.backgroundColor = color
|
||||
return gradeView
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - shadow
|
||||
extension UIView {
|
||||
|
||||
func setShadow(
|
||||
@@ -188,14 +161,14 @@ extension UIView {
|
||||
let xTranslation = (self.frame.width - shadowWidth) / 2 + (self.frame.width * xTranslationFactor)
|
||||
let yTranslation = (self.frame.height - shadowHeight) / 2 + (self.frame.height * yTranslationFactor)
|
||||
|
||||
let cornerRadius = self.frame.minSideSize * cornerRadiusFactor
|
||||
let cornerRadius = self.frame.minSide * cornerRadiusFactor
|
||||
|
||||
let shadowPath = UIBezierPath.init(
|
||||
roundedRect: CGRect.init(x: xTranslation, y: yTranslation, width: shadowWidth, height: shadowHeight),
|
||||
cornerRadius: cornerRadius
|
||||
)
|
||||
|
||||
let blurRadius = self.frame.minSideSize * blurRadiusFactor
|
||||
let blurRadius = self.frame.minSide * blurRadiusFactor
|
||||
|
||||
self.layer.shadowColor = UIColor.black.cgColor
|
||||
self.layer.shadowOffset = CGSize.zero
|
||||
@@ -249,7 +222,6 @@ extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - animation
|
||||
extension UIView {
|
||||
|
||||
func addCornerRadiusAnimation(to: CGFloat, duration: CFTimeInterval) {
|
||||
@@ -276,9 +248,12 @@ extension UIView {
|
||||
self.isHidden = true
|
||||
})
|
||||
}
|
||||
|
||||
func removeAllAnimations() {
|
||||
self.layer.removeAllAnimations()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - corner radius
|
||||
extension UIView {
|
||||
|
||||
func roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
|
||||
@@ -289,6 +264,6 @@ extension UIView {
|
||||
}
|
||||
|
||||
func round() {
|
||||
self.layer.cornerRadius = self.frame.minSideSize / 2
|
||||
self.layer.cornerRadius = self.frame.minSide / 2
|
||||
}
|
||||
}
|
||||
+8
-7
@@ -21,15 +21,16 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct SPApp {
|
||||
extension UIVisualEffectView {
|
||||
|
||||
static var udid: String? {
|
||||
return UIDevice.current.identifierForVendor?.uuidString
|
||||
convenience init(style: UIBlurEffect.Style) {
|
||||
let effect = UIBlurEffect(style: style)
|
||||
self.init(effect: effect)
|
||||
}
|
||||
|
||||
static var displayName: String? {
|
||||
return Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
|
||||
convenience init(vibrancy style: UIBlurEffect.Style) {
|
||||
let effect = UIBlurEffect(style: style)
|
||||
let vibrancyEffect = UIVibrancyEffect(blurEffect: effect)
|
||||
self.init(effect: vibrancyEffect)
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
+22
-10
@@ -21,22 +21,34 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPLayout {
|
||||
struct SPLayout {
|
||||
|
||||
static func sizeWith(widthFactor: CGFloat, maxWidth: CGFloat, heightFactor: CGFloat, maxHeight: CGFloat, relativeSideFactor: CGFloat, from relativeSize: CGSize) -> CGSize {
|
||||
static func sizeWith(widthFactor: CGFloat, maxWidth: CGFloat?, heightFactor: CGFloat, maxHeight: CGFloat?, relativeSideFactor: CGFloat?, from size: CGSize) -> CGSize {
|
||||
|
||||
var widthArea = relativeSize.width * widthFactor
|
||||
var heightArea = relativeSize.height * heightFactor
|
||||
var widthArea = size.width * widthFactor
|
||||
var heightArea = size.height * heightFactor
|
||||
|
||||
widthArea.setIfMore(when: maxWidth)
|
||||
heightArea.setIfMore(when: maxHeight)
|
||||
if let maxWidth = maxWidth {
|
||||
widthArea.setIfMore(when: maxWidth)
|
||||
}
|
||||
|
||||
if let maxHeight = maxHeight {
|
||||
heightArea.setIfMore(when: maxHeight)
|
||||
}
|
||||
|
||||
var prepareWidth = widthArea
|
||||
var prepareHeight = widthArea / relativeSideFactor
|
||||
if prepareHeight > heightArea {
|
||||
prepareHeight = heightArea
|
||||
prepareWidth = heightArea * relativeSideFactor
|
||||
var prepareHeight = heightArea
|
||||
|
||||
if let relativeSideFactor = relativeSideFactor {
|
||||
prepareHeight = widthArea / relativeSideFactor
|
||||
if prepareHeight > heightArea {
|
||||
prepareHeight = heightArea
|
||||
prepareWidth = heightArea * relativeSideFactor
|
||||
}
|
||||
}
|
||||
|
||||
return CGSize.init(width: prepareWidth, height: prepareHeight)
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// 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
|
||||
import LocalAuthentication
|
||||
|
||||
struct SPLocalAuthentication {
|
||||
|
||||
static var isEnable: Bool {
|
||||
let context = LAContext()
|
||||
var error: NSError?
|
||||
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
|
||||
return true
|
||||
} else {
|
||||
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func request(reason: String, complecton: @escaping (Bool)->()) {
|
||||
let context = LAContext()
|
||||
var error: NSError?
|
||||
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
|
||||
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, error in
|
||||
DispatchQueue.main.async { complecton(success) }
|
||||
}
|
||||
} else {
|
||||
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
|
||||
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, error in
|
||||
DispatchQueue.main.async { complecton(success) }
|
||||
}
|
||||
} else {
|
||||
complecton(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
+2
-3
@@ -28,7 +28,7 @@ struct SPMail {
|
||||
return MFMailComposeViewController.canSendMail()
|
||||
}
|
||||
|
||||
static func openMailApp(to email: String, subject: String? = nil, body: String? = nil) {
|
||||
static func openApp(to email: String, subject: String? = nil, body: String? = nil) {
|
||||
let parametrs = "mailto:\(email)?subject=\(subject ?? "")&body=\(body ?? "")".addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
|
||||
|
||||
if parametrs != nil {
|
||||
@@ -40,7 +40,7 @@ struct SPMail {
|
||||
}
|
||||
}
|
||||
|
||||
static func mailDialog(to email: String, subject: String? = nil, body: String? = nil, on viewController: UIViewController) {
|
||||
static func dialog(to email: String, subject: String? = nil, body: String? = nil, on viewController: UIViewController) {
|
||||
let mailVC = MFMailComposeViewController()
|
||||
mailVC.mailComposeDelegate = SPMailSingltone.sharedInstance
|
||||
|
||||
@@ -69,7 +69,6 @@ struct SPMail {
|
||||
private init() {}
|
||||
}
|
||||
|
||||
// Helper function inserted by Swift 4.2 migrator.
|
||||
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
|
||||
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// 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
|
||||
|
||||
enum SPNativeColors {
|
||||
|
||||
static let red = UIColor.init(hex: "FF3B30")
|
||||
static let orange = UIColor.init(hex: "FF9500")
|
||||
static let yellow = UIColor.init(hex: "FFCC00")
|
||||
static let green = UIColor.init(hex: "4CD964")
|
||||
static let tealBlue = UIColor.init(hex: "5AC8FA")
|
||||
static let blue = UIColor.init(hex: "007AFF")
|
||||
static let purple = UIColor.init(hex: "5856D6")
|
||||
static let pink = UIColor.init(hex: "FF2D55")
|
||||
static let white = UIColor.init(hex: "FFFFFF")
|
||||
static let customGray = UIColor.init(hex: "EFEFF4")
|
||||
static let lightGray = UIColor.init(hex: "E5E5EA")
|
||||
static let lightGray2 = UIColor.init(hex: "D1D1D6")
|
||||
static let midGray = UIColor.init(hex: "C7C7CC")
|
||||
static let gray = UIColor.init(hex: "8E8E93")
|
||||
static let black = UIColor.init(hex: "000000")
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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
|
||||
import UserNotifications
|
||||
|
||||
struct SPLocalNotification {
|
||||
|
||||
var identificator: String? = nil
|
||||
var text: String
|
||||
var title: String? = nil
|
||||
var badge: Int = 0
|
||||
var timeInterval: TimeInterval
|
||||
var soundEnabled: Bool = true
|
||||
var category: SPLocalNotificationCategory? = nil
|
||||
|
||||
init(after timeInterval: TimeInterval, text: String) {
|
||||
self.text = text
|
||||
self.timeInterval = timeInterval
|
||||
}
|
||||
|
||||
func add() {
|
||||
let identificator = self.identificator ?? "\(self.timeInterval)\(self.text)\(Int.random(min: 0, max: 1000))"
|
||||
|
||||
let notification = UNNotificationRequest(
|
||||
identifier: identificator,
|
||||
content: self.content,
|
||||
trigger: self.trigger
|
||||
)
|
||||
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.add(notification) { (error) in
|
||||
if let error = error {
|
||||
print("SPLocalNotification - \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var content: UNMutableNotificationContent {
|
||||
let content = UNMutableNotificationContent()
|
||||
content.body = self.text
|
||||
content.title = self.title ?? ""
|
||||
content.badge = NSNumber(value: UIApplication.shared.applicationIconBadgeNumber + self.badge)
|
||||
content.sound = self.soundEnabled ? UNNotificationSound.default : nil
|
||||
|
||||
if let category = self.category {
|
||||
if #available(iOS 12.0, *) {
|
||||
let notificationCategory = UNNotificationCategory(identifier: category.identifier, actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: nil, categorySummaryFormat: category.summary, options: [])
|
||||
UNUserNotificationCenter.current().setNotificationCategories([notificationCategory])
|
||||
content.categoryIdentifier = notificationCategory.identifier
|
||||
}
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
private var trigger: UNTimeIntervalNotificationTrigger {
|
||||
return UNTimeIntervalNotificationTrigger(timeInterval: self.timeInterval, repeats: false)
|
||||
}
|
||||
}
|
||||
|
||||
struct SPLocalNotificationCategory {
|
||||
|
||||
var identifier: String
|
||||
var summary: String
|
||||
|
||||
var countSymbol: String {
|
||||
return "%u"
|
||||
}
|
||||
}
|
||||
+30
-40
@@ -21,8 +21,9 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension String {
|
||||
public static func random(count: Int) -> String {
|
||||
extension String {
|
||||
|
||||
static func random(count: Int) -> String {
|
||||
let strings = [
|
||||
"В доме кардинала от меня не было тайн; не раз видел я, как он усердно перелистывает старинные книги и жадно роется в пыли фамильных рукописей. Когда я как-то упрекнул его за бесполезные бессонные ночи, после которых он впадал в болезненное уныние, он взглянул на меня с горькой улыбкой и раскрыл передо мною историю города Рима. В этой книге, в двадцатой главе жизнеописания папы Александра Шестого, я прочел следующие строки, навсегда оставшиеся в моей памяти",
|
||||
"По этому поводу между отцом и сыном завязался спор. Цезарь считал, что достаточно применить одно из тех средств, которые он всегда держал наготове для своих ближайших друзей, а именно: пресловутый ключ, которым то одного, то другого просили отпереть некий шкаф. На ключе был крохотный железный шип – недосмотр слесаря. Каждый, кто трудился над тугим замком, накалывал себе палец и на другой день умирал. Был еще перстень с львиной головой, который Цезарь надевал, когда хотел пожать руку той или иной особе. Лев впивался в кожу этих избранных рук, и через сутки наступала смерть.",
|
||||
@@ -33,58 +34,47 @@ public extension String {
|
||||
}
|
||||
}
|
||||
|
||||
public extension Bool {
|
||||
public static func random() -> Bool {
|
||||
return arc4random_uniform(2) == 0
|
||||
}
|
||||
}
|
||||
|
||||
public extension Int {
|
||||
public static func random(_ n: Int) -> Int {
|
||||
return Int(arc4random_uniform(UInt32(n)))
|
||||
extension Int {
|
||||
|
||||
static func random() -> Int {
|
||||
return Int.random(in: 0...Int.max)
|
||||
}
|
||||
|
||||
public static func random(min: Int, max: Int) -> Int {
|
||||
return Int(arc4random_uniform(UInt32(max - min - 1))) + min
|
||||
static func random(min: Int, max: Int) -> Int {
|
||||
return Int.random(in: min...max)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Double {
|
||||
public static func random() -> Double {
|
||||
return Double(arc4random()) / 0xFFFFFFFF
|
||||
extension Double {
|
||||
|
||||
static func random() -> Double {
|
||||
return Double.random(in: 0...Double.greatestFiniteMagnitude)
|
||||
}
|
||||
|
||||
public static func random(min: Double, max: Double) -> Double {
|
||||
return Double.random() * (max - min) + min
|
||||
static func random(min: Double, max: Double) -> Double {
|
||||
return Double.random(in: min...max)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Float {
|
||||
public static func random() -> Float {
|
||||
return Float(arc4random()) / 0xFFFFFFFF
|
||||
extension Float {
|
||||
|
||||
static func random() -> Float {
|
||||
return Float.random(in: 0...Float.greatestFiniteMagnitude)
|
||||
}
|
||||
|
||||
public static func random(min: Float, max: Float) -> Float {
|
||||
return Float.random() * (max - min) + min
|
||||
static func random(min: Float, max: Float) -> Float {
|
||||
return Float.random(in: min...max)
|
||||
}
|
||||
}
|
||||
|
||||
public extension CGFloat {
|
||||
public static func random() -> CGFloat {
|
||||
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
|
||||
extension CGFloat {
|
||||
|
||||
static func random() -> CGFloat {
|
||||
return CGFloat.random(in: 0...CGFloat.greatestFiniteMagnitude)
|
||||
}
|
||||
|
||||
public static func random(min: CGFloat, max: CGFloat) -> CGFloat {
|
||||
return CGFloat.random() * (max - min) + min
|
||||
}
|
||||
}
|
||||
|
||||
public extension Collection {
|
||||
/// Return a copy of `self` with its elements shuffled
|
||||
func shuffle() -> [Iterator.Element] {
|
||||
var list = Array(self)
|
||||
list.shuffleInPlace()
|
||||
return list
|
||||
static func random(min: CGFloat, max: CGFloat) -> CGFloat {
|
||||
return CGFloat.random(in: min...max)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,10 +85,10 @@ extension Collection where Index == Int {
|
||||
}
|
||||
}
|
||||
|
||||
public extension MutableCollection where Index == Int {
|
||||
/// Shuffle the elements of `self` in-place.
|
||||
extension MutableCollection where Index == Int {
|
||||
|
||||
mutating func shuffleInPlace() {
|
||||
// empty and single-element collections don't shuffle
|
||||
|
||||
if count < 2 { return }
|
||||
|
||||
for i in startIndex ..< endIndex - 1 {
|
||||
+3
-3
@@ -27,13 +27,13 @@ extension SPShadow {
|
||||
|
||||
private init() {}
|
||||
|
||||
public static func setFor(label: UILabel) {
|
||||
static func setFor(label: UILabel) {
|
||||
var offset = label.frame.height * 0.03
|
||||
offset.setIfMore(when: 1)
|
||||
label.setShadowOffsetForLetters(heightOffset: offset, opacity: 0.35)
|
||||
}
|
||||
|
||||
public static func setFor(view: UIView) {
|
||||
static func setFor(view: UIView) {
|
||||
|
||||
let xTranslationFactor: CGFloat = 0
|
||||
let yTranslationFactor: CGFloat = 0.18
|
||||
@@ -66,7 +66,7 @@ extension SPShadow {
|
||||
yTranslation = view.frame.height + maxBottomSpace - shadowHeight
|
||||
}
|
||||
|
||||
var blurRadius = view.frame.minSideSize * blurRadiusFactor
|
||||
var blurRadius = view.frame.minSide * blurRadiusFactor
|
||||
blurRadius.setIfMore(when: 10)
|
||||
blurRadius.setIfFewer(when: 7)
|
||||
|
||||
+2
-2
@@ -21,9 +21,9 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public struct SPShare {
|
||||
struct SPShare {
|
||||
|
||||
public struct Native {
|
||||
struct Native {
|
||||
|
||||
static func share(text: String? = nil, fileNames: [String] = [], images: [UIImage] = [], complection: ((_ isSharing: Bool)->())? = nil, sourceView: UIView, on viewController: UIViewController) {
|
||||
|
||||
+3
-8
@@ -24,11 +24,7 @@ import UIKit
|
||||
class SPInstagram {
|
||||
|
||||
static var isSetApp: Bool {
|
||||
if UIApplication.shared.canOpenURL(URL(string: "instagram://user?username=test")!) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return UIApplication.shared.canOpenURL(URL(string: "instagram://user?username=test")!)
|
||||
}
|
||||
|
||||
static func openPost(id: String) {
|
||||
@@ -38,7 +34,7 @@ class SPInstagram {
|
||||
if UIApplication.shared.canOpenURL(instagramUrl!) {
|
||||
UIApplication.shared.open(instagramUrl!, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
|
||||
} else {
|
||||
SPOpener.Link.redirectToBrowserAndOpen(link: safariURL)
|
||||
SPApp.open(link: safariURL.absoluteString, redirect: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,14 +45,13 @@ class SPInstagram {
|
||||
if UIApplication.shared.canOpenURL(instagramUrl!) {
|
||||
UIApplication.shared.open(instagramUrl!, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
|
||||
} else {
|
||||
SPOpener.Link.redirectToBrowserAndOpen(link: safariURL)
|
||||
SPApp.open(link: safariURL.absoluteString, redirect: true)
|
||||
}
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
// Helper function inserted by Swift 4.2 migrator.
|
||||
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
|
||||
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
|
||||
}
|
||||
+28
-9
@@ -24,11 +24,7 @@ import UIKit
|
||||
class SPTelegram {
|
||||
|
||||
static var isSetApp: Bool {
|
||||
if UIApplication.shared.canOpenURL(URL(string: "tg://msg?text=test")!) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return UIApplication.shared.canOpenURL(URL(string: "tg://msg?text=test")!)
|
||||
}
|
||||
|
||||
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
|
||||
@@ -47,7 +43,31 @@ class SPTelegram {
|
||||
|
||||
static func joinChannel(id: String) {
|
||||
let url = "https://t.me/joinchat/\(id)"
|
||||
SPOpener.Link.redirectToBrowserAndOpen(link: url)
|
||||
SPApp.open(link: url, redirect: true)
|
||||
}
|
||||
|
||||
static func joinChat(id: String) {
|
||||
|
||||
let openInBrowser = {
|
||||
let url = "https://t.me/joinchat/\(id)"
|
||||
SPApp.open(link: url, redirect: true)
|
||||
}
|
||||
|
||||
if SPTelegram.isSetApp {
|
||||
let urlStringEncoded = id.addingPercentEncoding( withAllowedCharacters: .urlHostAllowed)
|
||||
let urlOptional = URL(string: "tg://join?invite=\(urlStringEncoded ?? "")")
|
||||
if let url = urlOptional {
|
||||
if UIApplication.shared.canOpenURL(url) {
|
||||
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
|
||||
} else {
|
||||
openInBrowser()
|
||||
}
|
||||
} else {
|
||||
openInBrowser()
|
||||
}
|
||||
} else {
|
||||
openInBrowser()
|
||||
}
|
||||
}
|
||||
|
||||
static func openBot(username: String) {
|
||||
@@ -56,13 +76,12 @@ class SPTelegram {
|
||||
username.removeFirst()
|
||||
}
|
||||
let url = "https://telegram.me/\(username)"
|
||||
SPOpener.Link.redirectToBrowserAndOpen(link: url)
|
||||
SPApp.open(link: url, redirect: true)
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
// Helper function inserted by Swift 4.2 migrator.
|
||||
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
|
||||
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
|
||||
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
|
||||
}
|
||||
+1
-6
@@ -24,11 +24,7 @@ import UIKit
|
||||
class SPTwitter {
|
||||
|
||||
static var isSetApp: Bool {
|
||||
if UIApplication.shared.canOpenURL(URL(string: "twitter://post?message=test")!) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return UIApplication.shared.canOpenURL(URL(string: "twitter://post?message=test")!)
|
||||
}
|
||||
|
||||
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
|
||||
@@ -48,7 +44,6 @@ class SPTwitter {
|
||||
private init() {}
|
||||
}
|
||||
|
||||
// Helper function inserted by Swift 4.2 migrator.
|
||||
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
|
||||
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
|
||||
}
|
||||
+1
-8
@@ -24,11 +24,7 @@ import UIKit
|
||||
class SPViber {
|
||||
|
||||
static var isSetApp: Bool {
|
||||
if UIApplication.shared.canOpenURL(URL(string: "viber://forward?text=test")!) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return UIApplication.shared.canOpenURL(URL(string: "viber://forward?text=test")!)
|
||||
}
|
||||
|
||||
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
|
||||
@@ -48,9 +44,6 @@ class SPViber {
|
||||
private init() {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Helper function inserted by Swift 4.2 migrator.
|
||||
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
|
||||
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
|
||||
}
|
||||
+1
-6
@@ -24,11 +24,7 @@ import UIKit
|
||||
class SPWhatsApp {
|
||||
|
||||
static var isSetApp: Bool {
|
||||
if UIApplication.shared.canOpenURL(URL(string: "whatsapp://send?text=test")!) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return UIApplication.shared.canOpenURL(URL(string: "whatsapp://send?text=test")!)
|
||||
}
|
||||
|
||||
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
|
||||
@@ -48,7 +44,6 @@ class SPWhatsApp {
|
||||
private init() {}
|
||||
}
|
||||
|
||||
// Helper function inserted by Swift 4.2 migrator.
|
||||
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
|
||||
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// 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
|
||||
|
||||
enum SPStatusBar {
|
||||
case dark
|
||||
case light
|
||||
}
|
||||
|
||||
enum SPSystemIcon {
|
||||
case share
|
||||
case close
|
||||
case favorite
|
||||
case favorite_fill
|
||||
}
|
||||
|
||||
enum SPSocialNetwork {
|
||||
case whatsapp
|
||||
case telegram
|
||||
case vk
|
||||
case facebook
|
||||
case viber
|
||||
}
|
||||
|
||||
enum SPOauthState {
|
||||
case succsess
|
||||
case unvalidLogin
|
||||
case invalidLogin
|
||||
case unvalidPassword
|
||||
case invalidPassword
|
||||
case needTwoFactor
|
||||
case error
|
||||
}
|
||||
|
||||
enum SPSeparatorInsetStyle {
|
||||
case beforeImage
|
||||
case all
|
||||
case none
|
||||
case auto
|
||||
}
|
||||
|
||||
enum SPNavigationTitleStyle {
|
||||
case large
|
||||
case small
|
||||
case stork
|
||||
case noContent
|
||||
}
|
||||
|
||||
enum SPSystemApp {
|
||||
case photos
|
||||
case setting
|
||||
}
|
||||
|
||||
enum SPSelectionType {
|
||||
case select
|
||||
case unselect
|
||||
}
|
||||
+8
-18
@@ -26,34 +26,34 @@ 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 {
|
||||
case .base:
|
||||
self.backgroundColor = self.secondColor
|
||||
self.titleLabel?.font = UIFont.system(type: .Bold, size: 14)
|
||||
self.titleLabel?.font = UIFont.system(weight: .bold, size: 14)
|
||||
self.contentEdgeInsets = UIEdgeInsets.init(top: 6, left: 15, bottom: 6, right: 15)
|
||||
break
|
||||
case .main:
|
||||
self.backgroundColor = self.baseColor
|
||||
self.layer.borderWidth = 0
|
||||
self.setTitleColorForNoramlAndHightlightedStates(color: UIColor.white)
|
||||
self.titleLabel?.font = UIFont.system(type: .Bold, size: 14)
|
||||
self.setTitleColor(UIColor.white)
|
||||
self.titleLabel?.font = UIFont.system(weight: .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.titleLabel?.font = UIFont.system(type: .Bold, size: 14)
|
||||
self.setTitleColor(UIColor.white)
|
||||
self.titleLabel?.font = UIFont.system(weight: .bold, size: 14)
|
||||
self.contentEdgeInsets = UIEdgeInsets.init(top: 8, left: 15, bottom: 8, right: 15)
|
||||
break
|
||||
case .line:
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.layer.borderWidth = 1
|
||||
self.layer.borderColor = self.baseColor.cgColor
|
||||
self.titleLabel?.font = UIFont.system(type: .Medium, size: 14)
|
||||
self.titleLabel?.font = UIFont.system(weight: .medium, size: 14)
|
||||
self.contentEdgeInsets = UIEdgeInsets.init(top: 6, left: 15, bottom: 6, 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
|
||||
}
|
||||
+34
-24
@@ -21,35 +21,45 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPMengButton: SPGradientButton {
|
||||
class SPAppleMusicButton: SPButton {
|
||||
|
||||
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")
|
||||
var type: SPSelectionType = .unselect {
|
||||
didSet {
|
||||
self.updateType(animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
var selectColor: UIColor = UIColor.init(hex: "FD2D55") {
|
||||
didSet {
|
||||
self.updateType(animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
if self.isFrameRounded {
|
||||
self.gradientView.round()
|
||||
self.round()
|
||||
var baseColor: UIColor = UIColor.init(hex: "F8F7FC") {
|
||||
didSet {
|
||||
self.updateType(animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
override func commonInit() {
|
||||
super.commonInit()
|
||||
self.layer.cornerRadius = 8
|
||||
self.titleLabel?.font = UIFont.system(weight: .demiBold, size: 15)
|
||||
self.contentEdgeInsets = UIEdgeInsets.init(top: 12, left: 27, bottom: 12, right: 27)
|
||||
self.type = .unselect
|
||||
}
|
||||
|
||||
private func updateType(animated: Bool) {
|
||||
switch self.type {
|
||||
case .select:
|
||||
self.backgroundColor = self.selectColor
|
||||
self.setTitleColor(UIColor.white)
|
||||
break
|
||||
case .unselect:
|
||||
self.backgroundColor = self.baseColor
|
||||
self.setTitleColor(self.selectColor)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
// 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 SPAppleMusicSectionButtonsView: SPView {
|
||||
|
||||
let topSeparatorView = SPSeparatorView()
|
||||
let bottomSeparatorView = SPSeparatorView()
|
||||
let leftButton = SPAppleMusicButton()
|
||||
let rightButton = SPAppleMusicButton()
|
||||
|
||||
var sectionHeight: CGFloat = 92 {
|
||||
didSet { self.layoutSubviews() }
|
||||
}
|
||||
|
||||
var buttonsSpace: CGFloat = 18 {
|
||||
didSet { self.layoutSubviews() }
|
||||
}
|
||||
|
||||
var selectColor: UIColor = UIColor.init(hex: "FD2D55") {
|
||||
didSet {
|
||||
self.leftButton.selectColor = selectColor
|
||||
self.rightButton.selectColor = selectColor
|
||||
}
|
||||
}
|
||||
|
||||
var baseColor: UIColor = UIColor.init(hex: "F8F7FC") {
|
||||
didSet {
|
||||
self.leftButton.baseColor = baseColor
|
||||
self.rightButton.baseColor = baseColor
|
||||
}
|
||||
}
|
||||
|
||||
override func commonInit() {
|
||||
super.commonInit()
|
||||
self.addSubview(self.topSeparatorView)
|
||||
self.addSubview(self.bottomSeparatorView)
|
||||
|
||||
for button in [self.leftButton, self.rightButton] {
|
||||
button.type = .unselect
|
||||
button.setTitle("Title")
|
||||
self.addSubview(button)
|
||||
}
|
||||
}
|
||||
|
||||
func layout(origin: CGPoint, width: CGFloat) {
|
||||
self.frame.origin = origin
|
||||
self.frame.set(width: width, height: sectionHeight)
|
||||
self.layoutSubviews()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
self.topSeparatorView.frame.origin = .zero
|
||||
self.topSeparatorView.frame.set(width: self.frame.width)
|
||||
|
||||
self.bottomSeparatorView.frame.origin.x = 0
|
||||
self.bottomSeparatorView.frame.bottomY = self.frame.height
|
||||
self.bottomSeparatorView.frame.set(width: self.frame.width)
|
||||
|
||||
let buttonWidth = (self.frame.width - self.buttonsSpace) / 2
|
||||
|
||||
self.leftButton.sizeToFit()
|
||||
self.leftButton.frame.set(width: buttonWidth)
|
||||
self.leftButton.frame.origin.x = 0
|
||||
self.leftButton.center.y = self.frame.height / 2
|
||||
|
||||
self.rightButton.sizeToFit()
|
||||
self.rightButton.frame.set(width: buttonWidth)
|
||||
self.rightButton.frame.bottomX = self.frame.width
|
||||
self.rightButton.center.y = self.frame.height / 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// 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 SPButton: UIButton {
|
||||
|
||||
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
|
||||
if self.title(for: .normal) != nil {
|
||||
let inset: CGFloat = 6
|
||||
let sideSize = self.frame.height - inset * 2
|
||||
let titleFrame = self.titleRect(forContentRect: contentRect)
|
||||
return CGRect.init(x: titleFrame.origin.x - sideSize - 6, y: 0, width: sideSize, height: self.frame.height)
|
||||
} else {
|
||||
return super.imageRect(forContentRect: contentRect)
|
||||
}
|
||||
}
|
||||
|
||||
override var isHighlighted: Bool {
|
||||
didSet {
|
||||
if self.isHighlighted {
|
||||
self.imageView?.alpha = 0.7
|
||||
} else {
|
||||
self.imageView?.alpha = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var gradientView: SPGradientView? {
|
||||
didSet {
|
||||
self.gradientView?.isUserInteractionEnabled = false
|
||||
if self.gradientView?.superview == nil {
|
||||
if self.gradientView != nil {
|
||||
if self.imageView != nil {
|
||||
self.insertSubview(self.gradientView!, belowSubview: self.imageView!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var rounded: Bool = false {
|
||||
didSet {
|
||||
self.layoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
internal func commonInit() {
|
||||
self.adjustsImageWhenHighlighted = false
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.gradientView?.setSuperviewBounds()
|
||||
if self.rounded {
|
||||
self.round()
|
||||
}
|
||||
}
|
||||
|
||||
func set(enable: Bool, animatable: Bool) {
|
||||
self.isEnabled = enable
|
||||
if animatable {
|
||||
SPAnimation.animate(0.3, animations: {
|
||||
self.alpha = enable ? 1 : 0.6
|
||||
})
|
||||
} else {
|
||||
self.alpha = enable ? 1 : 0.6
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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 SPCircleCloseButton: SPSystemIconButton {
|
||||
|
||||
var side: CGFloat = 30
|
||||
|
||||
override init() {
|
||||
super.init(type: .close)
|
||||
self.rounded = true
|
||||
self.backgroundColor = SPNativeColors.customGray
|
||||
self.color = SPNativeColors.gray
|
||||
self.widthIconFactor = 0.36
|
||||
self.heightIconFactor = 0.36
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func layout(bottomX: CGFloat, y: CGFloat) {
|
||||
self.sizeToFit()
|
||||
self.frame.bottomX = bottomX
|
||||
self.frame.origin.y = y
|
||||
}
|
||||
|
||||
override func sizeToFit() {
|
||||
super.sizeToFit()
|
||||
self.frame.set(width: self.side)
|
||||
self.frame.set(height: self.side)
|
||||
}
|
||||
}
|
||||
@@ -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.5)
|
||||
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.frame.set(width: self.customSideSize)
|
||||
self.frame.set(height: 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.frame.set(width: sideSize)
|
||||
dotView.frame.set(height: sideSize)
|
||||
dotView.setYCenter()
|
||||
dotView.frame.origin.x = currentXPosition
|
||||
dotView.round()
|
||||
currentXPosition += (sideSize + space)
|
||||
}
|
||||
|
||||
self.round()
|
||||
}
|
||||
}
|
||||
+5
-7
@@ -21,10 +21,9 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPDownloadingButton: UIButton {
|
||||
class SPDownloadingButton: SPButton {
|
||||
|
||||
let activityIndicatorView = UIActivityIndicatorView.init()
|
||||
var isFrameRounded: Bool = false
|
||||
|
||||
func startLoading() {
|
||||
self.activityIndicatorView.alpha = 0
|
||||
@@ -39,7 +38,10 @@ class SPDownloadingButton: UIButton {
|
||||
})
|
||||
}
|
||||
|
||||
func stopLoading() {
|
||||
func stopLoading(newText: String? = nil) {
|
||||
if let newText = newText {
|
||||
self.setTitle(newText)
|
||||
}
|
||||
SPAnimation.animate(0.2, animations: {
|
||||
self.activityIndicatorView.alpha = 0
|
||||
}, withComplection: {
|
||||
@@ -53,10 +55,6 @@ class SPDownloadingButton: UIButton {
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.activityIndicatorView.center = CGPoint.init(x: self.frame.width / 2, y: self.frame.height / 2)
|
||||
|
||||
if self.isFrameRounded {
|
||||
self.round()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+26
-19
@@ -21,32 +21,32 @@
|
||||
|
||||
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() {
|
||||
self.titleLabel?.font = UIFont.system(type: UIFont.BoldType.DemiBold, size: 16)
|
||||
self.setTitleColorForNoramlAndHightlightedStates(color: UIColor.white)
|
||||
self.backgroundColor = SPNativeStyleKit.Colors.blue
|
||||
override func commonInit() {
|
||||
super.commonInit()
|
||||
self.titleLabel?.font = UIFont.system(weight: UIFont.FontWeight.demiBold, size: 16)
|
||||
self.setTitleColor(UIColor.white)
|
||||
self.backgroundColor = SPNativeColors.blue
|
||||
self.layer.masksToBounds = true
|
||||
self.layer.cornerRadius = 8
|
||||
self.contentEdgeInsets = UIEdgeInsets.init(top: 15, left: 15, bottom: 15, right: 15)
|
||||
@@ -58,8 +58,15 @@ class SPNativeOS11Button: SPDownloadingButton {
|
||||
let sideSpace: CGFloat = superview.frame.width * 0.112
|
||||
var width = superview.frame.width - sideSpace * 2
|
||||
width.setIfMore(when: 335)
|
||||
self.setWidth(width)
|
||||
self.frame.set(width: width)
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.gradientView?.setSuperviewBounds()
|
||||
self.gradientView?.layer.cornerRadius = self.layer.cornerRadius
|
||||
self.gradientView?.gradientLayer.cornerRadius = self.layer.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
+3
-5
@@ -39,7 +39,7 @@ class SPPlayCircleButton: UIButton {
|
||||
}
|
||||
}
|
||||
|
||||
var iconColor = SPNativeStyleKit.Colors.white {
|
||||
var iconColor = SPNativeColors.white {
|
||||
didSet {
|
||||
self.iconView.color = self.iconColor
|
||||
}
|
||||
@@ -52,7 +52,7 @@ class SPPlayCircleButton: UIButton {
|
||||
self.addSubview(self.iconView)
|
||||
self.iconView.isUserInteractionEnabled = false
|
||||
self.setTitle("", for: .normal)
|
||||
self.backgroundColor = SPNativeStyleKit.Colors.blue
|
||||
self.backgroundColor = SPNativeColors.blue
|
||||
self.audioState = .play
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class SPPlayCircleButton: UIButton {
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.iconView.setEqualsFrameFromBounds(self, withWidthFactor: 0.45, withHeightFactor: 0.45, withCentering: true)
|
||||
self.iconView.setBounds(self, withWidthFactor: 0.45, withHeightFactor: 0.45, withCentering: true)
|
||||
self.round()
|
||||
}
|
||||
|
||||
@@ -71,7 +71,5 @@ class SPPlayCircleButton: UIButton {
|
||||
case pause
|
||||
case stop
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+4
-4
@@ -21,7 +21,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPSocialIconButton: UIButton {
|
||||
class SPSocialButton: UIButton {
|
||||
|
||||
let iconView = SPSocialIconView.init()
|
||||
var widthIconFactor: CGFloat = 0.5
|
||||
@@ -75,13 +75,13 @@ class SPSocialIconButton: UIButton {
|
||||
fileprivate func commonInit() {
|
||||
self.iconView.isUserInteractionEnabled = false
|
||||
self.addSubview(self.iconView)
|
||||
self.backgroundColor = SPNativeStyleKit.Colors.blue
|
||||
self.iconView.color = SPNativeStyleKit.Colors.white
|
||||
self.backgroundColor = SPNativeColors.blue
|
||||
self.iconView.color = SPNativeColors.white
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.iconView.setEqualsFrameFromBounds(self, withWidthFactor: self.widthIconFactor, withHeightFactor: self.heightIconFactor, withCentering: true)
|
||||
self.iconView.setBounds(self, withWidthFactor: self.widthIconFactor, withHeightFactor: self.heightIconFactor, withCentering: true)
|
||||
self.round()
|
||||
}
|
||||
}
|
||||
+16
-15
@@ -21,19 +21,20 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPSystemIconButton: UIButton {
|
||||
class SPSystemIconButton: SPButton {
|
||||
|
||||
let iconView = SPSystemIconView.init()
|
||||
|
||||
var widthIconFactor: CGFloat = 1
|
||||
var heightIconFactor: CGFloat = 1
|
||||
|
||||
var type: SPSystemIconType {
|
||||
var icon: SPSystemIcon {
|
||||
didSet {
|
||||
self.iconView.type = self.type
|
||||
self.iconView.icon = self.icon
|
||||
}
|
||||
}
|
||||
|
||||
var color = SPNativeStyleKit.Colors.blue {
|
||||
var color = SPNativeColors.blue {
|
||||
didSet {
|
||||
self.iconView.color = self.color
|
||||
}
|
||||
@@ -49,17 +50,17 @@ class SPSystemIconButton: UIButton {
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
self.type = .share
|
||||
super.init(frame: CGRect.zero)
|
||||
override init() {
|
||||
self.icon = .share
|
||||
super.init()
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
init(type: SPSystemIconType) {
|
||||
self.type = type
|
||||
super.init(frame: CGRect.zero)
|
||||
self.iconView.type = self.type
|
||||
self.type = type
|
||||
init(type: SPSystemIcon) {
|
||||
self.icon = type
|
||||
super.init()
|
||||
self.iconView.icon = self.icon
|
||||
self.icon = type
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
@@ -67,14 +68,14 @@ class SPSystemIconButton: UIButton {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
fileprivate func commonInit() {
|
||||
override func commonInit() {
|
||||
super.commonInit()
|
||||
self.iconView.isUserInteractionEnabled = false
|
||||
self.addSubview(self.iconView)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.iconView.setEqualsFrameFromBounds(self, withWidthFactor: self.widthIconFactor, withHeightFactor: self.heightIconFactor, withCentering: true)
|
||||
self.iconView.setBounds(self, withWidthFactor: self.widthIconFactor, withHeightFactor: self.heightIconFactor, withCentering: true)
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -29,7 +29,7 @@ class SPAudioIconView: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
var color = SPNativeStyleKit.Colors.white {
|
||||
var color = SPNativeColors.white {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
+6
-2
@@ -29,7 +29,7 @@ class SPGolubevIconView: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
var whiteColor = SPNativeStyleKit.Colors.white {
|
||||
var whiteColor = SPNativeColors.white {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
@@ -103,10 +103,13 @@ class SPGolubevIconView: UIView {
|
||||
case .headphones:
|
||||
SPCodeDraw.GolubevIconPack.drawHeadphones(frame: rect, resizing: .aspectFit, white: self.whiteColor, light: self.lightColor, medium: self.mediumColor, dark: self.darkColor)
|
||||
break
|
||||
case .windmill:
|
||||
SPCodeDraw.GolubevIconPack.drawWindmill(frame: rect, resizing: .aspectFit, white: self.whiteColor, light: self.lightColor, medium: self.mediumColor, dark: self.darkColor)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public enum IconType {
|
||||
enum IconType {
|
||||
case camera
|
||||
case photoLibrary
|
||||
case ball
|
||||
@@ -116,6 +119,7 @@ class SPGolubevIconView: UIView {
|
||||
case documents
|
||||
case compass
|
||||
case headphones
|
||||
case windmill
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ class SPSocialIconView: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
var color = SPNativeStyleKit.Colors.blue {
|
||||
var color = SPNativeColors.blue {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
+6
-6
@@ -23,26 +23,26 @@ import UIKit
|
||||
|
||||
class SPSystemIconView: UIView {
|
||||
|
||||
var type: SPSystemIconType {
|
||||
var icon: SPSystemIcon {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
var color = SPNativeStyleKit.Colors.blue {
|
||||
var color = SPNativeColors.blue {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
self.type = .share
|
||||
self.icon = .share
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
init(type: SPSystemIconType) {
|
||||
self.type = type
|
||||
init(type: SPSystemIcon) {
|
||||
self.icon = type
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
@@ -57,7 +57,7 @@ class SPSystemIconView: UIView {
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
super.draw(rect)
|
||||
switch type {
|
||||
switch icon {
|
||||
case .share:
|
||||
SPCodeDraw.SystemIconPack.drawShare(frame: rect, resizing: .aspectFit, color: self.color)
|
||||
break
|
||||
+10
-8
@@ -21,22 +21,24 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPImageCollectionViewCell: SPCollectionContainerCell<SPDownloadingImageView> {
|
||||
class SPCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
var currentIndexPath: IndexPath?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.view.layer.cornerRadius = 10
|
||||
self.view.contentMode = .scaleAspectFill
|
||||
self.view.setNativeStyle()
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
internal func commonInit() {}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
self.view.contentMode = .scaleAspectFill
|
||||
self.view.setLoadingMode()
|
||||
self.currentIndexPath = nil
|
||||
}
|
||||
}
|
||||
+23
-25
@@ -21,35 +21,33 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIWindow {
|
||||
class SPImageCollectionViewCell: SPCollectionViewCell {
|
||||
|
||||
static var key: UIWindow? {
|
||||
return UIApplication.shared.keyWindow
|
||||
let imageView = SPDownloadingImageView()
|
||||
|
||||
override func commonInit() {
|
||||
super.commonInit()
|
||||
|
||||
self.addSubview(self.imageView)
|
||||
SPConstraints.setEqualSizeSuperview(for: self.imageView)
|
||||
|
||||
self.configure()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return topSafeArea
|
||||
private func configure() {
|
||||
self.currentIndexPath = nil
|
||||
self.imageView.removeImage()
|
||||
self.imageView.contentMode = .scaleAspectFill
|
||||
self.imageView.layer.masksToBounds = true
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return bottomSafeArea
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
self.configure()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.imageView.layer.cornerRadius = 12
|
||||
}
|
||||
}
|
||||
+5
-16
@@ -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)
|
||||
@@ -97,8 +86,8 @@ class SPMengTransformCollectionViewCell: SPCollectionViewCell {
|
||||
self.backgroundImageView.bottomAnchor.constraint(equalTo:
|
||||
contentView.bottomAnchor, constant: 0).isActive = true
|
||||
|
||||
self.gradientView.setStartColorPosition(SPGradientView.Position.TopLeft)
|
||||
self.gradientView.setEndColorPosition(.BottomRight)
|
||||
self.gradientView.startColorPosition = .topLeft
|
||||
self.gradientView.endColorPosition = .bottomRight
|
||||
self.gradientView.isHidden = true
|
||||
self.gradientView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.gradientView.layer.masksToBounds = false
|
||||
@@ -136,7 +125,7 @@ class SPMengTransformCollectionViewCell: SPCollectionViewCell {
|
||||
self.titleLabel.text = ""
|
||||
self.titleLabel.setDeepShadowForLetters()
|
||||
self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.titleLabel.font = UIFont.system(type: .DemiBold, size: 32)
|
||||
self.titleLabel.font = UIFont.system(weight: .demiBold, size: 32)
|
||||
self.titleLabel.textColor = UIColor.white
|
||||
self.titleLabel.numberOfLines = 0
|
||||
contentView.addSubview(self.titleLabel)
|
||||
@@ -151,7 +140,7 @@ class SPMengTransformCollectionViewCell: SPCollectionViewCell {
|
||||
self.subtitleLabel.text = ""
|
||||
self.subtitleLabel.setDeepShadowForLetters()
|
||||
self.subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.subtitleLabel.font = UIFont.system(type: .Regular, size: 17)
|
||||
self.subtitleLabel.font = UIFont.system(weight: .regular, size: 17)
|
||||
self.subtitleLabel.textColor = UIColor.white
|
||||
self.subtitleLabel.numberOfLines = 0
|
||||
contentView.addSubview(self.subtitleLabel)
|
||||
+32
-18
@@ -21,7 +21,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class SPCollectionViewLayout: UICollectionViewFlowLayout {
|
||||
class SPCollectionViewLayout: UICollectionViewFlowLayout {
|
||||
|
||||
var itemSpacingFactor: CGFloat = 0.11
|
||||
var minItemSpace: CGFloat = 0
|
||||
@@ -54,7 +54,7 @@ public class SPCollectionViewLayout: UICollectionViewFlowLayout {
|
||||
}
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class SPCollectionViewLayout: UICollectionViewFlowLayout {
|
||||
self.minimumLineSpacing = 0
|
||||
}
|
||||
|
||||
public override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
|
||||
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
|
||||
|
||||
if !self.isPaging {
|
||||
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
|
||||
@@ -86,7 +86,7 @@ public class SPCollectionViewLayout: UICollectionViewFlowLayout {
|
||||
} else {
|
||||
proposedContentOffset.x = round(rawPageValue) * self.pageWidth
|
||||
}
|
||||
return proposedContentOffset;
|
||||
return proposedContentOffset
|
||||
case .vertical:
|
||||
let rawPageValue = (self.collectionView!.contentOffset.y) / self.pageHeight
|
||||
|
||||
@@ -102,11 +102,13 @@ public class SPCollectionViewLayout: UICollectionViewFlowLayout {
|
||||
} else {
|
||||
proposedContentOffset.y = round(rawPageValue) * self.pageHeight
|
||||
}
|
||||
return proposedContentOffset;
|
||||
return proposedContentOffset
|
||||
default:
|
||||
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
|
||||
}
|
||||
}
|
||||
|
||||
override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
|
||||
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
|
||||
guard let collectionView = self.collectionView,
|
||||
let superAttributes = super.layoutAttributesForElements(in: rect) else {
|
||||
return super.layoutAttributesForElements(in: rect)
|
||||
@@ -155,11 +157,13 @@ public class SPCollectionViewLayout: UICollectionViewFlowLayout {
|
||||
owner.alpha = alpha
|
||||
}
|
||||
}
|
||||
default:
|
||||
return super.layoutAttributesForElements(in: rect)
|
||||
}
|
||||
return newAttributesArray
|
||||
}
|
||||
|
||||
override public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
|
||||
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -167,7 +171,7 @@ public class SPCollectionViewLayout: UICollectionViewFlowLayout {
|
||||
var deleteIndexPaths: [IndexPath] = []
|
||||
var insertIndexPaths: [IndexPath] = []
|
||||
|
||||
public override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
|
||||
override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
|
||||
super.prepare(forCollectionViewUpdates: updateItems)
|
||||
for item in updateItems {
|
||||
if item.updateAction == .delete {
|
||||
@@ -184,19 +188,18 @@ public class SPCollectionViewLayout: UICollectionViewFlowLayout {
|
||||
}
|
||||
}
|
||||
|
||||
public override func finalizeCollectionViewUpdates() {
|
||||
override func finalizeCollectionViewUpdates() {
|
||||
super.finalizeCollectionViewUpdates()
|
||||
self.insertIndexPaths.removeAll()
|
||||
self.deleteIndexPaths.removeAll()
|
||||
}
|
||||
|
||||
override public func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
|
||||
override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
|
||||
|
||||
let attributes: UICollectionViewLayoutAttributes? = super.initialLayoutAttributesForAppearingItem(at: itemIndexPath)
|
||||
|
||||
if isAllowInsertAnimation {
|
||||
if self.insertIndexPaths.contains(itemIndexPath) {
|
||||
//attributes?.center = CGPoint.init(x: attributes?.center.x ?? 0, y: 40)
|
||||
attributes?.alpha = 0
|
||||
attributes?.zIndex = 0
|
||||
attributes?.transform = CGAffineTransform.init(scaleX: self.minimumScaleFactor, y: self.minimumScaleFactor)
|
||||
@@ -206,12 +209,11 @@ public class SPCollectionViewLayout: UICollectionViewFlowLayout {
|
||||
return attributes
|
||||
}
|
||||
|
||||
override public func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
|
||||
var attributes: UICollectionViewLayoutAttributes?
|
||||
override func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
|
||||
|
||||
let attributes: UICollectionViewLayoutAttributes? = super.finalLayoutAttributesForDisappearingItem(at: itemIndexPath)
|
||||
|
||||
if self.deleteIndexPaths.contains(itemIndexPath) {
|
||||
|
||||
attributes = self.layoutAttributesForItem(at: itemIndexPath)
|
||||
attributes?.alpha = 0
|
||||
attributes?.zIndex = 0
|
||||
attributes?.transform3D = CATransform3DScale(CATransform3DIdentity, self.minimumScaleFactor, self.minimumScaleFactor, 1)
|
||||
@@ -220,7 +222,7 @@ public class SPCollectionViewLayout: UICollectionViewFlowLayout {
|
||||
return attributes
|
||||
}
|
||||
|
||||
override public func prepare() {
|
||||
override func prepare() {
|
||||
super.prepare()
|
||||
guard let collectionView = self.collectionView else {
|
||||
return
|
||||
@@ -229,9 +231,19 @@ public class SPCollectionViewLayout: UICollectionViewFlowLayout {
|
||||
collectionView.decelerationRate = UIScrollView.DecelerationRate.fast
|
||||
|
||||
if cellSideRatio == nil {
|
||||
var height = collectionView.bounds.size.height * self.heightFactor
|
||||
if height > self.maxHeight {
|
||||
height = self.maxHeight
|
||||
}
|
||||
|
||||
var width = collectionView.bounds.size.width * self.widthFactor
|
||||
if width > self.maxWidth {
|
||||
width = self.maxWidth
|
||||
}
|
||||
|
||||
self.itemSize = CGSize.init(
|
||||
width: collectionView.bounds.size.width * self.widthFactor,
|
||||
height: collectionView.bounds.size.height * self.heightFactor
|
||||
width: width,
|
||||
height: height
|
||||
)
|
||||
} else {
|
||||
self.itemSize = SPLayout.sizeWith(
|
||||
@@ -253,6 +265,8 @@ public class SPCollectionViewLayout: UICollectionViewFlowLayout {
|
||||
self.minimumLineSpacing = collectionView.frame.width * itemSpacingFactor
|
||||
case .vertical:
|
||||
self.minimumLineSpacing = collectionView.frame.height * itemSpacingFactor
|
||||
default:
|
||||
break
|
||||
}
|
||||
self.minimumLineSpacing.setIfMore(when: self.maxItemSpace)
|
||||
self.minimumLineSpacing.setIfFewer(when: self.minItemSpace)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user