Compare commits

...

125 Commits

Author SHA1 Message Date
Ivan Vorobei 962d1a937d Update to 1.2.2
Add new parametr - `colorIndicator` for change color arrow. Update example & Readme. Fix `SPStorkIndicatorView `
2019-01-31 21:20:53 +03:00
Ivan Vorobei bc6c7ff45e Update README.md 2019-01-31 08:06:47 +03:00
Ivan Vorobei ec7abd4a2a Update example
Update `SparrowKit` pod
2019-01-26 14:01:36 +03:00
Ivan Vorobei 6904e0916a Update UserInterfaceState.xcuserstate 2019-01-21 16:33:48 +03:00
Ivan Vorobei 90ee3d35da Add navigation controller for example 2019-01-21 15:15:54 +03:00
Ivan Vorobei 7f998787a4 Update to 1.2.1 version
Now if set custom height more default value, it aotu change to default value. Update example and pod version.
2019-01-18 13:51:49 +03:00
Ivan Vorobei 618da7d9ea Update README.md 2019-01-18 11:58:08 +03:00
Ivan Vorobei f062d07f94 Add Images 2019-01-15 20:31:03 +03:00
Ivan Vorobei 926e1f643c Add Images 2019-01-15 20:28:19 +03:00
Ivan Vorobei cdd8f4a2f4 Remove images 2019-01-13 14:28:54 +03:00
Ivan Vorobei 30cf10c595 Update README.md 2019-01-13 14:28:10 +03:00
Ivan Vorobei 884791b05c Create youtube3.jpg 2019-01-13 14:27:50 +03:00
Ivan Vorobei 4d0530e0a1 Update README.md 2019-01-13 14:25:42 +03:00
Ivan Vorobei 48741988b2 img 2019-01-13 14:25:20 +03:00
Ivan Vorobei 6297d5a5db Update youtube.jpg 2019-01-13 14:24:45 +03:00
Ivan Vorobei 3f60e48547 Update README.md 2019-01-13 14:23:44 +03:00
Ivan Vorobei 2ffe86afb9 Update youtube.jpg 2019-01-13 14:23:12 +03:00
Ivan Vorobei cf5bf12513 Update README.md 2019-01-13 14:18:03 +03:00
Ivan Vorobei 96aa1a6064 Create youtube.jpg 2019-01-13 14:15:38 +03:00
Ivan Vorobei 3241abd195 Delete SPStorkController-YouTube.jpg 2019-01-02 12:08:27 +03:00
Ivan Vorobei b72f10c7bd Update README.md 2019-01-02 12:08:06 +03:00
Ivan Vorobei 40bcff3cb6 Create SPStorkController-YouTube.jpg 2019-01-02 12:07:19 +03:00
Ivan Vorobei 01cc226253 Update README.md 2019-01-01 22:51:43 +03:00
Ivan Vorobei 691045d012 Update README.md 2019-01-01 22:49:49 +03:00
Ivan Vorobei 59f52f258a Update README.md 2018-12-31 18:50:40 +03:00
Ivan Vorobei bfece3194a Update README.md 2018-12-31 18:49:18 +03:00
Ivan Vorobei 6cccf24ffa Update README.md 2018-12-31 18:47:17 +03:00
Ivan Vorobei 635878e456 Update README.md 2018-12-30 18:55:52 +03:00
Ivan Vorobei d839e0f414 Update README.md 2018-12-30 18:54:01 +03:00
Ivan Vorobei e3cbc318be Update README.md 2018-12-30 18:51:38 +03:00
Ivan Vorobei e4232ec045 Update README.md 2018-12-29 17:31:29 +03:00
Ivan Vorobei 02346c0814 Update README.md 2018-12-28 21:55:13 +03:00
Ivan Vorobei 0b98593954 Update README.md 2018-12-28 21:54:36 +03:00
Ivan Vorobei 518700f9cd Delete spstork-preivew.jpg 2018-12-28 20:53:53 +03:00
Ivan Vorobei 5e02985ef2 Update README.md 2018-12-28 20:33:00 +03:00
Ivan Vorobei 539589e029 Update README.md 2018-12-28 19:50:04 +03:00
Ivan Vorobei 3434c487cd Update README.md 2018-12-28 19:48:53 +03:00
Ivan Vorobei c18e72add2 Update README.md 2018-12-28 19:46:59 +03:00
Ivan Vorobei c5e407fd80 Update README.md 2018-12-28 19:46:16 +03:00
Ivan Vorobei 3937c9caa5 Update README.md 2018-12-28 19:45:34 +03:00
Ivan Vorobei 38332bed39 Update README.md 2018-12-28 19:44:41 +03:00
Ivan Vorobei 40c7eee5de Update README.md 2018-12-28 19:43:48 +03:00
Ivan Vorobei 2e881179ee Update README.md 2018-12-28 19:42:10 +03:00
Ivan Vorobei 749640d359 Add video tutorial 2018-12-28 19:41:29 +03:00
Ivan Vorobei fd68fcb9f5 Update Readme 2018-12-28 18:44:07 +03:00
Ivan Vorobei 473f8ae0f1 Update README.md 2018-12-28 18:43:08 +03:00
Ivan Vorobei 75825b5e0a Update README 2018-12-28 18:42:17 +03:00
Ivan Vorobei f1e053d0d3 Update UserInterfaceState.xcuserstate 2018-12-24 16:49:58 +03:00
Ivan Vorobei 6208c93d40 Update README.md 2018-12-18 21:16:13 +03:00
Ivan Vorobei 8c64cd09da Update README.md 2018-12-18 16:32:23 +03:00
Ivan Vorobei c36078cda2 Update to 1.2
- After tap parrent controller auto dismiss
- Update example
2018-12-17 21:47:26 +03:00
Ivan Vorobei b78c82194f Update README.md 2018-12-17 20:02:13 +03:00
Ivan Vorobei a112473a04 Update Readme.md 2018-12-17 13:40:22 +03:00
Ivan Vorobei c1b135a30a Add example of TableView for SPStorkController
- Upadte example
- Update readme
- Add example table view for SPStorkController
2018-12-14 12:46:33 +03:00
Ivan Vorobei c1a7622a26 Update README.md 2018-12-13 23:29:42 +03:00
Ivan Vorobei c3421432b0 Update README.md 2018-12-13 19:58:12 +03:00
Ivan Vorobei 99353d3610 Update README.md 2018-12-12 17:01:17 +03:00
Ivan Vorobei 3bee6898e0 Update README.md 2018-12-12 16:54:24 +03:00
Ivan Vorobei 587790b1db Merge pull request #11 from jobinsjohn/patch-3
Updated ReadMe
2018-12-12 16:53:21 +03:00
Ivan Vorobei 2698cfe23d Merge pull request #12 from jobinsjohn/patch-4
Updated ReadMe
2018-12-12 16:52:48 +03:00
Jobins John 42b82886ca Updated ReadMe
Fixed typo in read me file
2018-12-12 17:44:46 +04:00
Jobins John 7af77b2596 Updated ReadMe file
Fixed typos
2018-12-12 17:43:36 +04:00
Ivan Vorobei 7c663e7760 Update README.md 2018-12-12 16:36:08 +03:00
Ivan Vorobei f561e94110 Update README.md 2018-12-12 16:35:47 +03:00
Ivan Vorobei 3bf06ae2b3 Update README.md 2018-12-12 16:35:27 +03:00
Ivan Vorobei a6d5e6f97d Merge pull request #10 from jobinsjohn/patch-2
Updated ReadMe
2018-12-12 16:34:35 +03:00
Ivan Vorobei 0f20afb87c Merge pull request #9 from jobinsjohn/patch-1
Updated ReadMe
2018-12-12 16:34:13 +03:00
Jobins John 2368747150 Updated Readme File
Added shields in read me for platform
2018-12-12 17:29:21 +04:00
Jobins John 32a010e1ca Updated ReadMe file
fixed typos and grammar mistakes
2018-12-12 17:23:49 +04:00
Ivan Vorobei fce0efaffa Update example 2018-12-12 16:10:45 +03:00
Ivan Vorobei 1500c272b1 Update to 1.1.8 version
- Fix bugs with paramtrs
- Add paramtr custom height
- Update Readme
2018-12-12 16:04:25 +03:00
Ivan Vorobei ba859e3646 Update README.md 2018-12-12 12:47:52 +03:00
Ivan Vorobei 01b8702c4a Update README.md 2018-12-12 12:46:22 +03:00
Ivan Vorobei ec809ee1c5 Update Readme.md 2018-12-12 12:28:53 +03:00
Ivan Vorobei a5f41ab3d6 Update gif 2018-12-12 12:27:47 +03:00
Ivan Vorobei 58554f1449 Update README.md 2018-12-12 11:43:18 +03:00
Ivan Vorobei 8c1b6e2c8d Add custom height 2018-12-10 21:39:48 +03:00
Ivan Vorobei 724635e387 Update README.md 2018-12-10 21:36:35 +03:00
Ivan Vorobei dfc0fb8e52 Update gif 2018-12-10 21:34:47 +03:00
Ivan Vorobei 50c9ab4104 Create gif-mockup.gif 2018-12-10 21:33:23 +03:00
Ivan Vorobei bb0821d2cc Update README.md 2018-12-07 13:45:03 +03:00
Ivan Vorobei ae29eba5b9 Update README.md 2018-12-07 13:44:30 +03:00
Ivan Vorobei 0c98c3c699 Update to 1.1.6 2018-12-07 13:40:49 +03:00
Ivan Vorobei 4ecc8e96d8 Update Readme.md 2018-12-07 13:40:13 +03:00
Ivan Vorobei fac454f41d remove source 2018-12-07 13:14:31 +03:00
Ivan Vorobei ae68a1be5d Update README.md 2018-12-06 17:33:44 +03:00
Ivan Vorobei c111a20c86 Update README.md 2018-12-06 17:28:05 +03:00
Ivan Vorobei cc1b1ac604 Update SPStorkPresentationController.swift 2018-12-06 17:14:37 +03:00
Ivan Vorobei 265f77dd5e Update README.md 2018-12-06 15:35:02 +03:00
Ivan Vorobei aacf153d0e Update README.md 2018-12-06 15:26:51 +03:00
Ivan Vorobei 5b8fdf6590 Update to 1.1.1 2018-12-06 15:21:50 +03:00
Ivan Vorobei 883ade3052 Update README.md 2018-12-06 15:08:41 +03:00
Ivan Vorobei 44837098ea Update to version 1.1 2018-12-06 15:05:47 +03:00
Ivan Vorobei 42063749ed Update README.md 2018-12-04 12:20:52 +03:00
Ivan Vorobei 464df2eb5c Update README.md 2018-12-04 12:19:31 +03:00
Ivan Vorobei 6f48df5761 Update README.md 2018-12-04 12:10:54 +03:00
Ivan Vorobei e331793afc Update README.md 2018-12-04 12:10:23 +03:00
Ivan Vorobei 82152b698a Update README.md 2018-12-03 14:33:11 +03:00
Ivan Vorobei 75eaaec598 Update Readme 2018-12-03 10:53:25 +03:00
Ivan Vorobei d2cc2e424c Update README.md 2018-12-02 21:13:33 +03:00
Ivan Vorobei 0ac35b4494 Update README.md 2018-12-02 21:13:18 +03:00
Ivan Vorobei 6c2600dea8 Update README.md 2018-12-01 23:36:18 +03:00
Ivan Vorobei 26d7422216 Update README.md 2018-12-01 23:35:01 +03:00
Ivan Vorobei a4a02b598f Update README.md 2018-11-30 20:54:40 +03:00
Ivan Vorobei 0309bb2e0e Update example and Readme 2018-11-30 20:43:35 +03:00
Ivan Vorobei 4e0507f132 Update README.md 2018-11-30 20:33:04 +03:00
Ivan Vorobei 8e33ff7614 Add donate banner 2018-11-30 20:32:36 +03:00
Ivan Vorobei b7ab9e0327 Update README.md 2018-11-30 20:30:39 +03:00
Ivan Vorobei 4129f23d00 Add header banner 2018-11-30 20:29:57 +03:00
Ivan Vorobei 91b01d83d9 Update Readme 2018-11-30 20:26:33 +03:00
Ivan Vorobei 4eda691bd0 Update README.md 2018-11-30 20:25:06 +03:00
Ivan Vorobei 0889352299 Update Readme 2018-11-30 20:18:38 +03:00
Ivan Vorobei 34f267d43c Update SPStorkController.podspec 2018-11-29 12:55:29 +03:00
Ivan Vorobei 425f8ec1ca Fix layout for SPStorkController
- Fix layout for SPStorkController
- Add extenshion for `CGRect` static var `displayFrame`. It is rect for screen size
- Set clear background color for `SPStorkIndicatorView`
2018-11-29 12:49:56 +03:00
Ivan Vorobei 25ea7eab4c Add more interactive to SPStorkController 2018-11-29 11:10:18 +03:00
Ivan Vorobei 16f060820f Update README.md 2018-11-28 20:11:41 +03:00
Ivan Vorobei 4c8faf7695 Update README.md 2018-11-28 17:49:37 +03:00
Ivan Vorobei 5b843582d4 Update README.md 2018-11-28 17:30:29 +03:00
Ivan Vorobei 8c58dbd2a3 Add example 2018-11-28 17:16:44 +03:00
Ivan Vorobei 366f6dffd1 Update Pospec 2018-11-28 17:16:12 +03:00
Ivan Vorobei 0141560c98 Create Example 2018-11-28 17:14:05 +03:00
Ivan Vorobei 900d4cbbc9 Add example 2018-11-28 17:13:23 +03:00
Ivan Vorobei dd8137b640 Update README.md 2018-11-28 17:07:21 +03:00
Ivan Vorobei 0118f80729 Update readme 2018-11-28 17:06:20 +03:00
Ivan Vorobei d0412a4285 Test submodule 2018-11-28 16:32:54 +03:00
150 changed files with 22818 additions and 6 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:stork-controller.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>stork-controller.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>
@@ -0,0 +1,47 @@
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let navigationController = UINavigationController(rootViewController: Controller())
self.launch(rootViewController: navigationController)
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// 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()
}
}
@@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<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="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<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="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
+43
View File
@@ -0,0 +1,43 @@
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.bottomYPosition + 10
self.view.addSubview(self.presentTableControllerButton)
}
@objc func presentModalViewController() {
let modal = ModalViewController()
let transitionDelegate = SPStorkTransitioningDelegate()
modal.transitioningDelegate = transitionDelegate
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)
}
}
@@ -0,0 +1,74 @@
// 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 {
scrollView.subviews.forEach {
$0.transform = CGAffineTransform(translationX: 0, y: -translation)
}
if presentationController.pan?.state != UIGestureRecognizer.State.changed {
presentationController.scrollViewDidScroll(translation)
}
} else {
presentationController.scrollViewDidScroll(0)
}
}
}
}
static 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() {}
}
extension UIViewController {
var isPresentedAsStork: Bool {
return transitioningDelegate is SPStorkTransitioningDelegate
&& modalPresentationStyle == .custom
&& presentingViewController != nil
}
}
@@ -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
final class SPStorkDismissingAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let presentedViewController = transitionContext.viewController(forKey: .from) else {
return
}
let containerView = transitionContext.containerView
let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: 1,
options: .curveEaseIn,
animations: {
presentedViewController.view.frame = offscreenFrame
}) { finished in
transitionContext.completeTransition(finished)
}
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.6
}
}
@@ -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 SPStorkIndicatorView: UIView {
var style: Style = .line {
didSet {
switch self.style {
case .line:
self.animate {
self.leftView.transform = .identity
self.rightView.transform = .identity
}
case .arrow:
self.animate {
let angle = CGFloat(20 * Float.pi / 180)
self.leftView.transform = CGAffineTransform.init(rotationAngle: angle)
self.rightView.transform = CGAffineTransform.init(rotationAngle: -angle)
}
}
}
}
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.color = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func sizeToFit() {
super.sizeToFit()
self.frame = CGRect.init(x: self.frame.origin.x, y: self.frame.origin.y, width: 36, height: 13)
let height: CGFloat = 5
let correction = height / 2
self.leftView.frame = CGRect.init(x: 0, y: 0, width: self.frame.width / 2 + correction, height: height)
self.leftView.center.y = self.frame.height / 2
self.leftView.layer.cornerRadius = min(self.leftView.frame.width, self.leftView.frame.height) / 2
self.rightView.frame = CGRect.init(x: self.frame.width / 2 - correction, y: 0, width: self.frame.width / 2 + correction, height: height)
self.rightView.center.y = self.frame.height / 2
self.rightView.layer.cornerRadius = min(self.leftView.frame.width, self.leftView.frame.height) / 2
}
private func animate(animations: @escaping (() -> Void)) {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.beginFromCurrentState, .curveEaseOut], animations: {
animations()
})
}
enum Style {
case arrow
case line
}
}
@@ -0,0 +1,433 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPStorkPresentationController: UIPresentationController, UIGestureRecognizerDelegate {
var isSwipeToDismissEnabled: Bool = true
var isTapAroundToDismissEnabled: Bool = true
var showIndicator: Bool = true
var indicatorColor: UIColor = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1)
var customHeight: CGFloat? = nil
var transitioningDelegate: SPStorkTransitioningDelegate?
var pan: UIPanGestureRecognizer?
var tap: UITapGestureRecognizer?
private var indicatorView = SPStorkIndicatorView()
private var gradeView: UIView = UIView()
private let snapshotViewContainer = UIView()
private var snapshotView: UIView?
private let backgroundView = UIView()
private var snapshotViewTopConstraint: NSLayoutConstraint?
private var snapshotViewWidthConstraint: NSLayoutConstraint?
private var snapshotViewAspectRatioConstraint: NSLayoutConstraint?
private var workGester: Bool = false
private var startDismissing: Bool = false
private var topSpace: CGFloat {
let statusBarHeight: CGFloat = UIApplication.shared.statusBarFrame.height
return (statusBarHeight < 25) ? 30 : statusBarHeight
}
private var alpha: CGFloat {
return 0.51
}
private var cornerRadius: CGFloat {
return 10
}
private var scaleForPresentingView: CGFloat {
guard let containerView = containerView else { return 0 }
let factor = 1 - (self.topSpace * 2 / containerView.frame.height)
return factor
}
override var frameOfPresentedViewInContainerView: CGRect {
guard let containerView = containerView else { return .zero }
var customHeight = self.customHeight ?? containerView.bounds.height
if customHeight > containerView.bounds.height {
customHeight = containerView.bounds.height
print("SPStorkController - Custom height change to default value. Your height more maximum value")
}
let additionTranslate = containerView.bounds.height - customHeight
let yOffset: CGFloat = self.topSpace + 13 + additionTranslate
return CGRect(x: 0, y: yOffset, width: containerView.bounds.width, height: containerView.bounds.height - yOffset)
}
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
guard let containerView = self.containerView, let presentedView = self.presentedView, let window = containerView.window else { return }
if self.showIndicator {
self.indicatorView.color = self.indicatorColor
presentedView.addSubview(self.indicatorView)
}
self.updateLayoutIndicator()
self.indicatorView.style = .arrow
self.gradeView.alpha = 0
let initialFrame: CGRect = presentingViewController.isPresentedAsStork ? presentingViewController.view.frame : containerView.bounds
containerView.insertSubview(self.snapshotViewContainer, belowSubview: presentedViewController.view)
self.snapshotViewContainer.frame = initialFrame
self.updateSnapshot()
self.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 = self.alpha
snapshotView.layer.cornerRadius = self.cornerRadius
snapshotView.layer.masksToBounds = true
rootSnapshotView = snapshotView
let snapshotRoundedView = UIView()
snapshotRoundedView.layer.cornerRadius = self.cornerRadius
snapshotRoundedView.layer.masksToBounds = true
containerView.insertSubview(snapshotRoundedView, aboveSubview: snapshotView)
snapshotRoundedView.frame = initialFrame
snapshotRoundedView.transform = transformForSnapshotView
rootSnapshotRoundedView = snapshotRoundedView
}
presentedViewController.transitionCoordinator?.animate(
alongsideTransition: { [weak self] context in
guard let `self` = self else { return }
self.snapshotView?.transform = transformForSnapshotView
self.gradeView.alpha = self.alpha
}, completion: { _ in
self.snapshotView?.transform = .identity
rootSnapshotView?.removeFromSuperview()
rootSnapshotRoundedView?.removeFromSuperview()
})
}
override func presentationTransitionDidEnd(_ completed: Bool) {
super.presentationTransitionDidEnd(completed)
guard let containerView = containerView else { return }
self.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.isTapAroundToDismissEnabled {
self.tap = UITapGestureRecognizer.init(target: self, action: #selector(self.handleTap))
self.tap?.cancelsTouchesInView = false
self.snapshotViewContainer.addGestureRecognizer(self.tap!)
}
if self.isSwipeToDismissEnabled {
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 handleTap() {
self.presentedViewController.dismiss(animated: true, completion: nil)
}
override func dismissalTransitionWillBegin() {
super.dismissalTransitionWillBegin()
guard let containerView = containerView else { return }
self.startDismissing = true
let initialFrame: CGRect = presentingViewController.isPresentedAsStork ? presentingViewController.view.frame : containerView.bounds
let initialTransform = CGAffineTransform.identity
.translatedBy(x: 0, y: -initialFrame.origin.y)
.translatedBy(x: 0, y: self.topSpace)
.translatedBy(x: 0, y: -initialFrame.height / 2)
.scaledBy(x: scaleForPresentingView, y: scaleForPresentingView)
.translatedBy(x: 0, y: initialFrame.height / 2)
self.snapshotViewTopConstraint?.isActive = false
self.snapshotViewWidthConstraint?.isActive = false
self.snapshotViewAspectRatioConstraint?.isActive = false
self.snapshotViewContainer.translatesAutoresizingMaskIntoConstraints = true
self.snapshotViewContainer.frame = initialFrame
self.snapshotViewContainer.transform = initialTransform
let finalCornerRadius = presentingViewController.isPresentedAsStork ? self.cornerRadius : 0
let finalTransform: CGAffineTransform = .identity
self.addCornerRadiusAnimation(for: self.snapshotView, cornerRadius: finalCornerRadius, duration: 0.6)
var rootSnapshotView: UIView?
var rootSnapshotRoundedView: UIView?
if presentingViewController.isPresentedAsStork {
guard let rootController = presentingViewController.presentingViewController, let snapshotView = rootController.view.snapshotView(afterScreenUpdates: false) else { return }
containerView.insertSubview(snapshotView, aboveSubview: backgroundView)
snapshotView.frame = initialFrame
snapshotView.transform = initialTransform
rootSnapshotView = snapshotView
snapshotView.layer.cornerRadius = self.cornerRadius
snapshotView.layer.masksToBounds = true
let snapshotRoundedView = UIView()
snapshotRoundedView.layer.cornerRadius = self.cornerRadius
snapshotRoundedView.layer.masksToBounds = true
snapshotRoundedView.backgroundColor = UIColor.black.withAlphaComponent(1 - self.alpha)
containerView.insertSubview(snapshotRoundedView, aboveSubview: snapshotView)
snapshotRoundedView.frame = initialFrame
snapshotRoundedView.transform = initialTransform
rootSnapshotRoundedView = snapshotRoundedView
}
presentedViewController.transitionCoordinator?.animate(
alongsideTransition: { [weak self] context in
guard let `self` = self else { return }
self.snapshotView?.transform = .identity
self.snapshotViewContainer.transform = finalTransform
self.gradeView.alpha = 0
}, completion: { _ in
rootSnapshotView?.removeFromSuperview()
rootSnapshotRoundedView?.removeFromSuperview()
})
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
super.dismissalTransitionDidEnd(completed)
guard let containerView = containerView else { return }
self.backgroundView.removeFromSuperview()
self.snapshotView?.removeFromSuperview()
self.snapshotViewContainer.removeFromSuperview()
let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
presentedViewController.view.frame = offscreenFrame
presentedViewController.view.transform = .identity
}
}
extension SPStorkPresentationController {
@objc func handlePan(gestureRecognizer: UIPanGestureRecognizer) {
guard gestureRecognizer.isEqual(pan), self.isSwipeToDismissEnabled else { return }
switch gestureRecognizer.state {
case .began:
self.workGester = true
self.indicatorView.style = .line
self.presentingViewController.view.layer.removeAllAnimations()
self.presentingViewController.view.endEditing(true)
self.presentedViewController.view.endEditing(true)
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: containerView)
case .changed:
self.workGester = true
if self.isSwipeToDismissEnabled {
let translation = gestureRecognizer.translation(in: presentedView)
self.updatePresentedViewForTranslation(inVerticalDirection: translation.y)
} else {
gestureRecognizer.setTranslation(.zero, in: presentedView)
}
case .ended:
self.workGester = false
let translation = gestureRecognizer.translation(in: presentedView).y
if translation >= 240 {
presentedViewController.dismiss(animated: true, completion: nil)
} else {
self.indicatorView.style = .arrow
UIView.animate(
withDuration: 0.6,
delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: 1,
options: [.curveEaseOut, .allowUserInteraction],
animations: {
self.snapshotView?.transform = .identity
self.presentedView?.transform = .identity
self.gradeView.alpha = self.alpha
})
}
default:
break
}
}
func scrollViewDidScroll(_ translation: CGFloat) {
if !self.workGester {
self.updatePresentedViewForTranslation(inVerticalDirection: translation)
}
}
func updatePresentingController() {
if self.startDismissing { return }
self.updateSnapshot()
}
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 factor = 1 + (translationForModal / 6000)
self.snapshotView?.transform = CGAffineTransform.init(scaleX: factor, y: factor)
self.gradeView.alpha = self.alpha - ((factor - 1) * 15)
}
}
}
extension SPStorkPresentationController {
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
guard let containerView = containerView else { return }
self.updateSnapshotAspectRatio()
if presentedViewController.view.isDescendant(of: containerView) {
UIView.animate(withDuration: 0.1) { [weak self] in
guard let `self` = self else { return }
self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView
}
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { contex in
self.updateLayoutIndicator()
}, completion: { [weak self] _ in
self?.updateSnapshotAspectRatio()
self?.updateSnapshot()
})
}
private func updateLayoutIndicator() {
guard let presentedView = self.presentedView else { return }
self.indicatorView.style = .line
self.indicatorView.sizeToFit()
self.indicatorView.frame.origin.y = 12
self.indicatorView.center.x = presentedView.frame.width / 2
}
private func updateSnapshot() {
guard let currentSnapshotView = presentingViewController.view.snapshotView(afterScreenUpdates: true) else { return }
self.snapshotView?.removeFromSuperview()
self.snapshotViewContainer.addSubview(currentSnapshotView)
self.constraints(view: currentSnapshotView, to: self.snapshotViewContainer)
self.snapshotView = currentSnapshotView
self.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
}
}
@@ -0,0 +1,55 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
final class SPStorkPresentingAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let presentedViewController = transitionContext.viewController(forKey: .to) else {
return
}
let containerView = transitionContext.containerView
containerView.addSubview(presentedViewController.view)
presentedViewController.view.frame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
let finalFrameForPresentedView = transitionContext.finalFrame(for: presentedViewController)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: 1,
options: .curveEaseOut,
animations: {
presentedViewController.view.frame = finalFrameForPresentedView
}, completion: { finished in
transitionContext.completeTransition(finished)
})
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.6
}
}
@@ -0,0 +1,50 @@
// 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 final class SPStorkTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
public var isSwipeToDismissEnabled: Bool = true
public var isTapAroundToDismissEnabled: Bool = true
public var showIndicator: Bool = true
public var indicatorColor: UIColor = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1)
public var customHeight: CGFloat? = nil
public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
let controller = SPStorkPresentationController(presentedViewController: presented, presenting: presenting)
controller.isSwipeToDismissEnabled = self.isSwipeToDismissEnabled
controller.isTapAroundToDismissEnabled = self.isTapAroundToDismissEnabled
controller.showIndicator = self.showIndicator
controller.indicatorColor = self.indicatorColor
controller.customHeight = self.customHeight
controller.transitioningDelegate = self
return controller
}
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return SPStorkPresentingAnimationController()
}
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return SPStorkDismissingAnimationController()
}
}
@@ -0,0 +1,65 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPAnimation {
static func animate(_ duration: TimeInterval,
animations: (() -> Void)!,
delay: TimeInterval = 0,
options: UIView.AnimationOptions = [],
withComplection completion: (() -> Void)! = {}) {
UIView.animate(
withDuration: duration,
delay: delay,
options: options,
animations: {
animations()
}, completion: { finished in
completion()
})
}
static func animateWithRepeatition(_ duration: TimeInterval,
animations: (() -> Void)!,
delay: TimeInterval = 0,
options: UIView.AnimationOptions = [],
withComplection completion: (() -> Void)! = {}) {
var optionsWithRepeatition = options
optionsWithRepeatition.insert([.autoreverse, .repeat])
self.animate(
duration,
animations: {
animations()
},
delay: delay,
options: optionsWithRepeatition,
withComplection: {
completion()
})
}
}
@@ -0,0 +1,103 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPAnimationAlpha {
fileprivate static let durationListAnimation: TimeInterval = 0.45
fileprivate static let coefLenthForTransition: CGFloat = 2.8
fileprivate static let delayPerItem: TimeInterval = 0.09
static func hideList(_ duration: TimeInterval = durationListAnimation,
views: [UIView],
delayPerItem: TimeInterval = delayPerItem,
withComplection completion: (() -> Void)! = {}) {
var del: Double = 0
for view in views {
delay(del, closure: {
if (view == views.last) {
SPAnimation.animate(duration, animations: {
view.alpha = 0
}, withComplection: {
completion()
})
} else {
SPAnimation.animate(duration, animations: {
view.alpha = 0
})
}
})
del += delayPerItem
}
}
static func hideReverseList(_ duration: TimeInterval = durationListAnimation,
views: [UIView],
delayPerItem: TimeInterval = delayPerItem,
withComplection completion: (() -> Void)! = {}) {
var del: Double = 0
let reversedViews = views.reversed()
for view in reversedViews {
delay(del, closure: {
if (view == views.last) {
SPAnimation.animate(duration, animations: {
view.alpha = 0
}, withComplection: {
completion()
})
} else {
SPAnimation.animate(duration, animations: {
view.alpha = 0
})
}
})
del += delayPerItem
}
}
static func showList(_ duration: TimeInterval = durationListAnimation,
views: [UIView],
delayPerItem: TimeInterval = delayPerItem,
withComplection completion: (() -> Void)! = {}) {
var del: Double = 0
for view in views {
delay(del, closure: {
if (view == views.last) {
SPAnimation.animate(duration, animations: {
view.alpha = 1
}, withComplection: {
completion()
})
} else {
SPAnimation.animate(duration, animations: {
view.alpha = 1
})
}
})
del += delayPerItem
}
}
}
@@ -0,0 +1,73 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPAnimationSpring {
fileprivate static let spring: CGFloat = 1
fileprivate static let velocity: CGFloat = 1
static func animate(_ duration: TimeInterval,
animations: (() -> Void)!,
delay: TimeInterval = 0,
spring: CGFloat = spring,
velocity: CGFloat = velocity,
options: UIView.AnimationOptions = [],
withComplection completion: (() -> Void)! = {}) {
UIView.animate(
withDuration: duration,
delay: delay,
usingSpringWithDamping: spring,
initialSpringVelocity: velocity,
options: options,
animations: {
animations()
}, completion: { finished in
completion()
})
}
static func animateWithRepeatition(_ duration: TimeInterval,
animations: (() -> Void)!,
delay: TimeInterval = 0,
spring: CGFloat = spring,
velocity: CGFloat = velocity,
options: UIView.AnimationOptions = [],
withComplection completion: (() -> Void)! = {}) {
var optionsWithRepeatition = options
optionsWithRepeatition.insert([.autoreverse, .repeat])
UIView.animate(
withDuration: duration,
delay: delay,
usingSpringWithDamping: spring,
initialSpringVelocity: velocity,
options: optionsWithRepeatition,
animations: {
animations()
}, completion: { finished in
completion()
})
}
}
@@ -0,0 +1,119 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPAnimationUpward {
fileprivate static let durationListAnimation: TimeInterval = 0.45
fileprivate static let coefLenthForTransition: CGFloat = 2.8
fileprivate static let delayPerItem: TimeInterval = 0.09
static func hide(_ duration: TimeInterval,
view: UIView,
delay: TimeInterval = 0,
withComplection completion: (() -> Void)! = {}) {
var options: UIView.AnimationOptions = []
options.insert(.curveEaseIn)
SPAnimationSpring.animate(
duration, animations: {
view.alpha = 0
view.frame.origin.y = view.frame.origin.y + (-UIScreen.main.bounds.height / coefLenthForTransition)
},
delay: delay,
options: options,
withComplection: {
completion()
})
}
static func hideList(_ duration: TimeInterval = durationListAnimation,
views: [UIView],
delayPerItem: TimeInterval = delayPerItem,
withComplection completion: (() -> Void)! = {}) {
var del: Double = 0
for view in views {
delay(del, closure: {
if (view == views.last) {
SPAnimationUpward.hide(duration, view: view, withComplection: {
completion()
})
} else {
SPAnimationUpward.hide(duration, view: view)
}
})
del += delayPerItem
}
}
static func show(_ duration: TimeInterval,
view: UIView,
delay: TimeInterval = 0,
withComplection completion: (() -> Void)! = {}) {
view.alpha = 0
view.isHidden = false
view.transform = CGAffineTransform(translationX: 0, y: UIScreen.main.bounds.height / coefLenthForTransition)
var options: UIView.AnimationOptions = []
options.insert(.curveEaseOut)
SPAnimationSpring.animate(
duration, animations: {
view.alpha = 1
view.transform = CGAffineTransform.identity
},
delay: delay,
options: options,
withComplection: {
completion()
})
}
static func showList(_ duration: TimeInterval = durationListAnimation,
views: [UIView],
delayPerItem: TimeInterval = delayPerItem,
options: UIView.AnimationOptions = [],
withComplection completion: (() -> Void)! = {}) {
var del: Double = 0
for view in views {
delay(del, closure: {
if (view == views.last) {
SPAnimationUpward.show(duration, view: view, withComplection: {
completion()
})
} else {
SPAnimationUpward.show(duration, view: view)
}
})
del += delayPerItem
}
}
}
@@ -0,0 +1,68 @@
// 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 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() {}
}
@@ -0,0 +1,44 @@
// 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 SPApp {
public struct Badge {
static var number: Int {
get {
return UIApplication.shared.applicationIconBadgeNumber
}
set {
UIApplication.shared.applicationIconBadgeNumber = newValue
}
}
static func reset() {
UIApplication.shared.applicationIconBadgeNumber = 0
}
private init() {}
}
}
@@ -0,0 +1,47 @@
// 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 SPApp {
struct Launch {
static func run() {
self.count += 1
}
static var count: Int {
get {
return UserDefaults.standard.value(forKey: "SPLaunchCount") as? Int ?? 0
}
set {
UserDefaults.standard.set(newValue, forKey: "SPLaunchCount")
}
}
static var isFirstLaunch: Bool {
return (self.count == 1) || (self.count == 0)
}
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)})
}
@@ -0,0 +1,139 @@
// 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 SPAppStore {
static func link(appID: String) -> String {
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)"),
UIApplication.shared.canOpenURL(url) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
}
}
static func requestReview(appID: String, force: Bool) {
if force {
if let url = URL(string: "itms-apps://itunes.apple.com/us/app/apple-store/id\(appID)?mt=8&action=write-review"),
UIApplication.shared.canOpenURL(url) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
}
} else {
if #available(iOS 10.3, *) {
SKStoreReviewController.requestReview()
}
}
}
static func requestReview() {
if #available(iOS 10.3, *) {
SKStoreReviewController.requestReview()
}
}
static func isUpdateAvailable(completion: @escaping (Bool)->()) {
guard let info = Bundle.main.infoDictionary,
let currentVersion = info["CFBundleShortVersionString"] as? String,
let identifier = info["CFBundleIdentifier"] as? String,
let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)") else {
completion(false)
return
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
if let error = error { throw error }
guard let data = data else {
completion(false)
return
}
let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any]
guard let result = (json?["results"] as? [Any])?.first as? [String: Any], let version = result["version"] as? String else {
completion(false)
return
}
let compareResult = currentVersion.compare(version, options: .numeric)
DispatchQueue.main.async {
completion(compareResult == .orderedAscending)
}
} catch {
completion(false)
}
}
task.resume()
}
private init() {}
}
extension NSRange {
func range(for str: String) -> Range<String.Index>? {
guard location != NSNotFound else { return nil }
guard let fromUTFIndex = str.utf16.index(str.utf16.startIndex, offsetBy: location, limitedBy: str.utf16.endIndex) else { return nil }
guard let toUTFIndex = str.utf16.index(fromUTFIndex, offsetBy: length, limitedBy: str.utf16.endIndex) else { return nil }
guard let fromIndex = String.Index(fromUTFIndex, within: str) else { return nil }
guard let toIndex = String.Index(toUTFIndex, within: str) else { return nil }
return fromIndex ..< toIndex
}
}
extension String {
func ranges(of string: String, options: CompareOptions = .literal) -> [Range<Index>] {
var result: [Range<Index>] = []
var start = startIndex
while let range = range(of: string, options: options, range: start..<endIndex) {
result.append(range)
start = range.lowerBound < range.upperBound ? range.upperBound : index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
}
return result
}
func slices(from: String, to: String) -> [Substring] {
let pattern = "(?<=" + from + ").*?(?=" + to + ")"
return ranges(of: pattern, options: .regularExpression)
.map{ self[$0] }
}
}
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
import AVFoundation
public 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 {
}
}
private init() {}
}
fileprivate func convertFromAVAudioSessionCategory(_ input: AVAudioSession.Category) -> String {
return input.rawValue
}
@@ -0,0 +1,57 @@
// 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 AVFoundation
public class SPAudioPlayer: NSObject, AVAudioPlayerDelegate {
fileprivate var player: AVAudioPlayer = AVAudioPlayer()
fileprivate var endPlayingComplection: (()->())? = nil
func play(fileName: String, complection: (()->())? = nil) {
self.endPlayingComplection?()
self.player = AVAudioPlayer()
let url = Bundle.main.url(forResource: fileName, withExtension: nil)
if url == nil {
self.endPlayingComplection?()
return
}
do {
self.player = try AVAudioPlayer(contentsOf: url!)
player.volume = 1
player.delegate = self
player.prepareToPlay()
player.play()
self.endPlayingComplection = complection
} catch let error as NSError {
print(error.description)
}
}
func stop() {
player.stop()
}
public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
self.endPlayingComplection?()
}
}
@@ -0,0 +1,37 @@
// 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 SPBufer {
static var text: String? {
get {
return UIPasteboard.general.string
}
set {
UIPasteboard.general.string = newValue
}
}
private init() {}
}
@@ -0,0 +1,161 @@
// 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 SPCodeDraw {
public 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)) {
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 200, height: 200), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 200, y: resizedFrame.height / 200)
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 177.85, y: 86.98))
bezierPath.addLine(to: CGPoint(x: 54.26, y: 9.11))
bezierPath.addCurve(to: CGPoint(x: 42.64, y: 7.3), controlPoint1: CGPoint(x: 50.61, y: 6.94), controlPoint2: CGPoint(x: 46.07, y: 7.3))
bezierPath.addCurve(to: CGPoint(x: 29, y: 20.56), controlPoint1: CGPoint(x: 28.94, y: 7.3), controlPoint2: CGPoint(x: 29, y: 17.88))
bezierPath.addLine(to: CGPoint(x: 29, y: 179.72))
bezierPath.addCurve(to: CGPoint(x: 42.64, y: 192.98), controlPoint1: CGPoint(x: 29, y: 181.98), controlPoint2: CGPoint(x: 28.94, y: 192.98))
bezierPath.addCurve(to: CGPoint(x: 54.26, y: 191.16), controlPoint1: CGPoint(x: 46.07, y: 192.98), controlPoint2: CGPoint(x: 50.61, y: 193.34))
bezierPath.addLine(to: CGPoint(x: 177.85, y: 113.3))
bezierPath.addCurve(to: CGPoint(x: 186.24, y: 100.14), controlPoint1: CGPoint(x: 187.99, y: 107.26), controlPoint2: CGPoint(x: 186.24, y: 100.14))
bezierPath.addCurve(to: CGPoint(x: 177.85, y: 86.98), controlPoint1: CGPoint(x: 186.24, y: 100.14), controlPoint2: CGPoint(x: 187.99, y: 93.02))
bezierPath.close()
bezierPath.usesEvenOddFillRule = true
fillColor.setFill()
bezierPath.fill()
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)) {
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 200, height: 200), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 200, y: resizedFrame.height / 200)
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 70.11, y: 6.86))
bezierPath.addLine(to: CGPoint(x: 34.32, y: 6.86))
bezierPath.addCurve(to: CGPoint(x: 20, y: 21.18), controlPoint1: CGPoint(x: 26.41, y: 6.86), controlPoint2: CGPoint(x: 20, y: 13.27))
bezierPath.addLine(to: CGPoint(x: 20, y: 178.68))
bezierPath.addCurve(to: CGPoint(x: 34.32, y: 193), controlPoint1: CGPoint(x: 20, y: 186.59), controlPoint2: CGPoint(x: 26.41, y: 193))
bezierPath.addLine(to: CGPoint(x: 70.11, y: 193))
bezierPath.addCurve(to: CGPoint(x: 84.43, y: 178.68), controlPoint1: CGPoint(x: 78.02, y: 193), controlPoint2: CGPoint(x: 84.43, y: 186.59))
bezierPath.addLine(to: CGPoint(x: 84.43, y: 21.18))
bezierPath.addCurve(to: CGPoint(x: 70.11, y: 6.86), controlPoint1: CGPoint(x: 84.43, y: 13.27), controlPoint2: CGPoint(x: 78.02, y: 6.86))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 163.19, y: 6.86))
bezierPath.addLine(to: CGPoint(x: 127.39, y: 6.86))
bezierPath.addCurve(to: CGPoint(x: 113.07, y: 21.18), controlPoint1: CGPoint(x: 119.48, y: 6.86), controlPoint2: CGPoint(x: 113.07, y: 13.27))
bezierPath.addLine(to: CGPoint(x: 113.07, y: 178.68))
bezierPath.addCurve(to: CGPoint(x: 127.39, y: 193), controlPoint1: CGPoint(x: 113.07, y: 186.59), controlPoint2: CGPoint(x: 119.48, y: 193))
bezierPath.addLine(to: CGPoint(x: 163.19, y: 193))
bezierPath.addCurve(to: CGPoint(x: 177.5, y: 178.68), controlPoint1: CGPoint(x: 171.09, y: 193), controlPoint2: CGPoint(x: 177.5, y: 186.59))
bezierPath.addLine(to: CGPoint(x: 177.5, y: 21.18))
bezierPath.addCurve(to: CGPoint(x: 163.19, y: 6.86), controlPoint1: CGPoint(x: 177.5, y: 13.27), controlPoint2: CGPoint(x: 171.09, y: 6.86))
bezierPath.close()
bezierPath.usesEvenOddFillRule = true
fillColor.setFill()
bezierPath.fill()
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)) {
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 200, height: 200), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 200, y: resizedFrame.height / 200)
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 177.82, y: 7.86))
bezierPath.addLine(to: CGPoint(x: 20.32, y: 7.86))
bezierPath.addCurve(to: CGPoint(x: 6, y: 22.18), controlPoint1: CGPoint(x: 12.41, y: 7.86), controlPoint2: CGPoint(x: 6, y: 14.27))
bezierPath.addLine(to: CGPoint(x: 6, y: 179.68))
bezierPath.addCurve(to: CGPoint(x: 20.32, y: 194), controlPoint1: CGPoint(x: 6, y: 187.59), controlPoint2: CGPoint(x: 12.41, y: 194))
bezierPath.addLine(to: CGPoint(x: 177.82, y: 194))
bezierPath.addCurve(to: CGPoint(x: 192.15, y: 179.68), controlPoint1: CGPoint(x: 185.73, y: 194), controlPoint2: CGPoint(x: 192.15, y: 187.59))
bezierPath.addLine(to: CGPoint(x: 192.15, y: 22.18))
bezierPath.addCurve(to: CGPoint(x: 177.82, y: 7.86), controlPoint1: CGPoint(x: 192.15, y: 14.27), controlPoint2: CGPoint(x: 185.73, y: 7.86))
bezierPath.close()
bezierPath.usesEvenOddFillRule = true
fillColor.setFill()
bezierPath.fill()
context.restoreGState()
}
@objc(StyleKitNameResizingBehavior)
public 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 {
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() {}
}
}
@@ -0,0 +1,24 @@
// 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 SPCodeDraw { private init() {} }
@@ -0,0 +1,486 @@
// 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 SPCodeDraw {
public 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)) {
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 40, height: 40), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 40, y: resizedFrame.height / 40)
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 19.9, y: 0.99))
bezierPath.addCurve(to: CGPoint(x: 12.12, y: 1.1), controlPoint1: CGPoint(x: 14.77, y: 0.99), controlPoint2: CGPoint(x: 14.13, y: 1.01))
bezierPath.addCurve(to: CGPoint(x: 7.54, y: 1.98), controlPoint1: CGPoint(x: 10.11, y: 1.19), controlPoint2: CGPoint(x: 8.74, y: 1.51))
bezierPath.addCurve(to: CGPoint(x: 4.19, y: 4.15), controlPoint1: CGPoint(x: 6.29, y: 2.46), controlPoint2: CGPoint(x: 5.24, y: 3.1))
bezierPath.addCurve(to: CGPoint(x: 2.02, y: 7.5), controlPoint1: CGPoint(x: 3.14, y: 5.2), controlPoint2: CGPoint(x: 2.5, y: 6.26))
bezierPath.addCurve(to: CGPoint(x: 1.14, y: 12.08), controlPoint1: CGPoint(x: 1.55, y: 8.7), controlPoint2: CGPoint(x: 1.23, y: 10.07))
bezierPath.addCurve(to: CGPoint(x: 1.03, y: 19.86), controlPoint1: CGPoint(x: 1.05, y: 14.09), controlPoint2: CGPoint(x: 1.03, y: 14.73))
bezierPath.addCurve(to: CGPoint(x: 1.14, y: 27.64), controlPoint1: CGPoint(x: 1.03, y: 24.98), controlPoint2: CGPoint(x: 1.05, y: 25.63))
bezierPath.addCurve(to: CGPoint(x: 2.02, y: 32.22), controlPoint1: CGPoint(x: 1.23, y: 29.65), controlPoint2: CGPoint(x: 1.55, y: 31.02))
bezierPath.addCurve(to: CGPoint(x: 4.19, y: 35.56), controlPoint1: CGPoint(x: 2.5, y: 33.46), controlPoint2: CGPoint(x: 3.14, y: 34.51))
bezierPath.addCurve(to: CGPoint(x: 7.54, y: 37.74), controlPoint1: CGPoint(x: 5.24, y: 36.61), controlPoint2: CGPoint(x: 6.29, y: 37.26))
bezierPath.addCurve(to: CGPoint(x: 12.12, y: 38.62), controlPoint1: CGPoint(x: 8.74, y: 38.21), controlPoint2: CGPoint(x: 10.11, y: 38.52))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 38.73), controlPoint1: CGPoint(x: 14.13, y: 38.71), controlPoint2: CGPoint(x: 14.77, y: 38.73))
bezierPath.addCurve(to: CGPoint(x: 27.68, y: 38.62), controlPoint1: CGPoint(x: 25.02, y: 38.73), controlPoint2: CGPoint(x: 25.67, y: 38.71))
bezierPath.addCurve(to: CGPoint(x: 32.26, y: 37.74), controlPoint1: CGPoint(x: 29.69, y: 38.52), controlPoint2: CGPoint(x: 31.06, y: 38.21))
bezierPath.addCurve(to: CGPoint(x: 35.6, y: 35.56), controlPoint1: CGPoint(x: 33.5, y: 37.26), controlPoint2: CGPoint(x: 34.55, y: 36.61))
bezierPath.addCurve(to: CGPoint(x: 37.78, y: 32.22), controlPoint1: CGPoint(x: 36.65, y: 34.51), controlPoint2: CGPoint(x: 37.3, y: 33.46))
bezierPath.addCurve(to: CGPoint(x: 38.66, y: 27.64), controlPoint1: CGPoint(x: 38.25, y: 31.02), controlPoint2: CGPoint(x: 38.56, y: 29.65))
bezierPath.addCurve(to: CGPoint(x: 38.77, y: 19.86), controlPoint1: CGPoint(x: 38.75, y: 25.63), controlPoint2: CGPoint(x: 38.77, y: 24.98))
bezierPath.addCurve(to: CGPoint(x: 38.66, y: 12.08), controlPoint1: CGPoint(x: 38.77, y: 14.73), controlPoint2: CGPoint(x: 38.75, y: 14.09))
bezierPath.addCurve(to: CGPoint(x: 37.78, y: 7.5), controlPoint1: CGPoint(x: 38.56, y: 10.07), controlPoint2: CGPoint(x: 38.25, y: 8.7))
bezierPath.addCurve(to: CGPoint(x: 35.6, y: 4.15), controlPoint1: CGPoint(x: 37.3, y: 6.26), controlPoint2: CGPoint(x: 36.65, y: 5.2))
bezierPath.addCurve(to: CGPoint(x: 32.26, y: 1.98), controlPoint1: CGPoint(x: 34.55, y: 3.1), controlPoint2: CGPoint(x: 33.5, y: 2.46))
bezierPath.addCurve(to: CGPoint(x: 27.68, y: 1.1), controlPoint1: CGPoint(x: 31.06, y: 1.51), controlPoint2: CGPoint(x: 29.69, y: 1.19))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 0.99), controlPoint1: CGPoint(x: 25.67, y: 1.01), controlPoint2: CGPoint(x: 25.02, y: 0.99))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 19.9, y: 4.39))
bezierPath.addCurve(to: CGPoint(x: 27.52, y: 4.5), controlPoint1: CGPoint(x: 24.94, y: 4.39), controlPoint2: CGPoint(x: 25.53, y: 4.41))
bezierPath.addCurve(to: CGPoint(x: 31.03, y: 5.15), controlPoint1: CGPoint(x: 29.36, y: 4.58), controlPoint2: CGPoint(x: 30.36, y: 4.89))
bezierPath.addCurve(to: CGPoint(x: 33.2, y: 6.56), controlPoint1: CGPoint(x: 31.91, y: 5.49), controlPoint2: CGPoint(x: 32.54, y: 5.9))
bezierPath.addCurve(to: CGPoint(x: 34.61, y: 8.73), controlPoint1: CGPoint(x: 33.86, y: 7.22), controlPoint2: CGPoint(x: 34.27, y: 7.85))
bezierPath.addCurve(to: CGPoint(x: 35.26, y: 12.23), controlPoint1: CGPoint(x: 34.87, y: 9.39), controlPoint2: CGPoint(x: 35.18, y: 10.39))
bezierPath.addCurve(to: CGPoint(x: 35.37, y: 19.86), controlPoint1: CGPoint(x: 35.35, y: 14.22), controlPoint2: CGPoint(x: 35.37, y: 14.82))
bezierPath.addCurve(to: CGPoint(x: 35.26, y: 27.48), controlPoint1: CGPoint(x: 35.37, y: 24.9), controlPoint2: CGPoint(x: 35.35, y: 25.49))
bezierPath.addCurve(to: CGPoint(x: 34.61, y: 30.99), controlPoint1: CGPoint(x: 35.18, y: 29.32), controlPoint2: CGPoint(x: 34.87, y: 30.32))
bezierPath.addCurve(to: CGPoint(x: 33.2, y: 33.16), controlPoint1: CGPoint(x: 34.27, y: 31.87), controlPoint2: CGPoint(x: 33.86, y: 32.5))
bezierPath.addCurve(to: CGPoint(x: 31.03, y: 34.57), controlPoint1: CGPoint(x: 32.54, y: 33.82), controlPoint2: CGPoint(x: 31.91, y: 34.23))
bezierPath.addCurve(to: CGPoint(x: 27.52, y: 35.22), controlPoint1: CGPoint(x: 30.36, y: 34.83), controlPoint2: CGPoint(x: 29.36, y: 35.14))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 35.33), controlPoint1: CGPoint(x: 25.53, y: 35.31), controlPoint2: CGPoint(x: 24.94, y: 35.33))
bezierPath.addCurve(to: CGPoint(x: 12.27, y: 35.22), controlPoint1: CGPoint(x: 14.86, y: 35.33), controlPoint2: CGPoint(x: 14.26, y: 35.31))
bezierPath.addCurve(to: CGPoint(x: 8.77, y: 34.57), controlPoint1: CGPoint(x: 10.43, y: 35.14), controlPoint2: CGPoint(x: 9.43, y: 34.83))
bezierPath.addCurve(to: CGPoint(x: 6.6, y: 33.16), controlPoint1: CGPoint(x: 7.89, y: 34.23), controlPoint2: CGPoint(x: 7.26, y: 33.82))
bezierPath.addCurve(to: CGPoint(x: 5.19, y: 30.99), controlPoint1: CGPoint(x: 5.94, y: 32.5), controlPoint2: CGPoint(x: 5.53, y: 31.87))
bezierPath.addCurve(to: CGPoint(x: 4.54, y: 27.48), controlPoint1: CGPoint(x: 4.93, y: 30.32), controlPoint2: CGPoint(x: 4.62, y: 29.32))
bezierPath.addCurve(to: CGPoint(x: 4.43, y: 19.86), controlPoint1: CGPoint(x: 4.45, y: 25.49), controlPoint2: CGPoint(x: 4.43, y: 24.9))
bezierPath.addCurve(to: CGPoint(x: 4.54, y: 12.23), controlPoint1: CGPoint(x: 4.43, y: 14.82), controlPoint2: CGPoint(x: 4.45, y: 14.22))
bezierPath.addCurve(to: CGPoint(x: 5.19, y: 8.73), controlPoint1: CGPoint(x: 4.62, y: 10.39), controlPoint2: CGPoint(x: 4.93, y: 9.39))
bezierPath.addCurve(to: CGPoint(x: 6.6, y: 6.56), controlPoint1: CGPoint(x: 5.53, y: 7.85), controlPoint2: CGPoint(x: 5.94, y: 7.22))
bezierPath.addCurve(to: CGPoint(x: 8.77, y: 5.15), controlPoint1: CGPoint(x: 7.26, y: 5.9), controlPoint2: CGPoint(x: 7.89, y: 5.49))
bezierPath.addCurve(to: CGPoint(x: 12.27, y: 4.5), controlPoint1: CGPoint(x: 9.43, y: 4.89), controlPoint2: CGPoint(x: 10.43, y: 4.58))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 4.39), controlPoint1: CGPoint(x: 14.26, y: 4.41), controlPoint2: CGPoint(x: 14.86, y: 4.39))
bezierPath.addLine(to: CGPoint(x: 19.9, y: 4.39))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 19.9, y: 26.15))
bezierPath.addCurve(to: CGPoint(x: 13.61, y: 19.86), controlPoint1: CGPoint(x: 16.42, y: 26.15), controlPoint2: CGPoint(x: 13.61, y: 23.33))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 13.57), controlPoint1: CGPoint(x: 13.61, y: 16.38), controlPoint2: CGPoint(x: 16.42, y: 13.57))
bezierPath.addCurve(to: CGPoint(x: 26.19, y: 19.86), controlPoint1: CGPoint(x: 23.37, y: 13.57), controlPoint2: CGPoint(x: 26.19, y: 16.38))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 26.15), controlPoint1: CGPoint(x: 26.19, y: 23.33), controlPoint2: CGPoint(x: 23.37, y: 26.15))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 19.9, y: 10.17))
bezierPath.addCurve(to: CGPoint(x: 10.21, y: 19.86), controlPoint1: CGPoint(x: 14.55, y: 10.17), controlPoint2: CGPoint(x: 10.21, y: 14.51))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 29.55), controlPoint1: CGPoint(x: 10.21, y: 25.21), controlPoint2: CGPoint(x: 14.55, y: 29.55))
bezierPath.addCurve(to: CGPoint(x: 29.59, y: 19.86), controlPoint1: CGPoint(x: 25.25, y: 29.55), controlPoint2: CGPoint(x: 29.59, y: 25.21))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 10.17), controlPoint1: CGPoint(x: 29.59, y: 14.51), controlPoint2: CGPoint(x: 25.25, y: 10.17))
bezierPath.addLine(to: CGPoint(x: 19.9, y: 10.17))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 32.43, y: 9.57))
bezierPath.addCurve(to: CGPoint(x: 30.23, y: 11.77), controlPoint1: CGPoint(x: 32.43, y: 10.79), controlPoint2: CGPoint(x: 31.44, y: 11.77))
bezierPath.addCurve(to: CGPoint(x: 28.03, y: 9.57), controlPoint1: CGPoint(x: 29.01, y: 11.77), controlPoint2: CGPoint(x: 28.03, y: 10.79))
bezierPath.addCurve(to: CGPoint(x: 30.23, y: 7.38), controlPoint1: CGPoint(x: 28.03, y: 8.36), controlPoint2: CGPoint(x: 29.01, y: 7.38))
bezierPath.addCurve(to: CGPoint(x: 32.43, y: 9.57), controlPoint1: CGPoint(x: 31.44, y: 7.38), controlPoint2: CGPoint(x: 32.43, y: 8.36))
bezierPath.close()
fillColor.setFill()
bezierPath.fill()
context.restoreGState()
}
@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)) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()!
//// Resize to Target Frame
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 40, height: 40), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 40, y: resizedFrame.height / 40)
//// Bezier Drawing
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 32.57, y: 19.79))
bezierPath.addCurve(to: CGPoint(x: 38.07, y: 10.51), controlPoint1: CGPoint(x: 32.57, y: 19.79), controlPoint2: CGPoint(x: 37.57, y: 12.8))
bezierPath.addCurve(to: CGPoint(x: 37.01, y: 9.23), controlPoint1: CGPoint(x: 38.24, y: 9.69), controlPoint2: CGPoint(x: 37.87, y: 9.23))
bezierPath.addCurve(to: CGPoint(x: 32.67, y: 9.23), controlPoint1: CGPoint(x: 37.01, y: 9.23), controlPoint2: CGPoint(x: 34.13, y: 9.23))
bezierPath.addCurve(to: CGPoint(x: 31.01, y: 10.28), controlPoint1: CGPoint(x: 31.67, y: 9.23), controlPoint2: CGPoint(x: 31.31, y: 9.66))
bezierPath.addCurve(to: CGPoint(x: 25.8, y: 18.34), controlPoint1: CGPoint(x: 31.01, y: 10.28), controlPoint2: CGPoint(x: 28.66, y: 15.23))
bezierPath.addCurve(to: CGPoint(x: 23.91, y: 19.66), controlPoint1: CGPoint(x: 24.89, y: 19.35), controlPoint2: CGPoint(x: 24.42, y: 19.66))
bezierPath.addCurve(to: CGPoint(x: 23.32, y: 18.41), controlPoint1: CGPoint(x: 23.5, y: 19.66), controlPoint2: CGPoint(x: 23.32, y: 19.32))
bezierPath.addLine(to: CGPoint(x: 23.32, y: 10.44))
bezierPath.addCurve(to: CGPoint(x: 22.25, y: 9), controlPoint1: CGPoint(x: 23.32, y: 9.33), controlPoint2: CGPoint(x: 23.18, y: 9))
bezierPath.addLine(to: CGPoint(x: 15.29, y: 9))
bezierPath.addCurve(to: CGPoint(x: 14.43, y: 9.72), controlPoint1: CGPoint(x: 14.76, y: 9), controlPoint2: CGPoint(x: 14.43, y: 9.31))
bezierPath.addCurve(to: CGPoint(x: 16.05, y: 13.85), controlPoint1: CGPoint(x: 14.43, y: 10.77), controlPoint2: CGPoint(x: 16.05, y: 11.01))
bezierPath.addLine(to: CGPoint(x: 16.05, y: 19.72))
bezierPath.addCurve(to: CGPoint(x: 15.42, y: 21.36), controlPoint1: CGPoint(x: 16.05, y: 20.9), controlPoint2: CGPoint(x: 15.99, y: 21.36))
bezierPath.addCurve(to: CGPoint(x: 8.39, y: 10.51), controlPoint1: CGPoint(x: 13.93, y: 21.36), controlPoint2: CGPoint(x: 10.38, y: 16.28))
bezierPath.addCurve(to: CGPoint(x: 6.44, y: 9), controlPoint1: CGPoint(x: 8, y: 9.33), controlPoint2: CGPoint(x: 7.57, y: 9))
bezierPath.addLine(to: CGPoint(x: 2.09, y: 9))
bezierPath.addCurve(to: CGPoint(x: 1, y: 10.05), controlPoint1: CGPoint(x: 1.46, y: 9), controlPoint2: CGPoint(x: 1, y: 9.43))
bezierPath.addCurve(to: CGPoint(x: 7.7, y: 23.62), controlPoint1: CGPoint(x: 1, y: 11.2), controlPoint2: CGPoint(x: 2.36, y: 16.51))
bezierPath.addCurve(to: CGPoint(x: 20.23, y: 31), controlPoint1: CGPoint(x: 11.28, y: 28.41), controlPoint2: CGPoint(x: 15.99, y: 31))
bezierPath.addCurve(to: CGPoint(x: 23.45, y: 29.49), controlPoint1: CGPoint(x: 22.82, y: 31), controlPoint2: CGPoint(x: 23.45, y: 30.57))
bezierPath.addLine(to: CGPoint(x: 23.45, y: 25.82))
bezierPath.addCurve(to: CGPoint(x: 24.34, y: 24.51), controlPoint1: CGPoint(x: 23.45, y: 24.9), controlPoint2: CGPoint(x: 23.81, y: 24.51))
bezierPath.addCurve(to: CGPoint(x: 28.46, y: 27.1), controlPoint1: CGPoint(x: 24.94, y: 24.51), controlPoint2: CGPoint(x: 25.99, y: 24.7))
bezierPath.addCurve(to: CGPoint(x: 33.16, y: 31), controlPoint1: CGPoint(x: 31.37, y: 29.85), controlPoint2: CGPoint(x: 31.57, y: 31))
bezierPath.addLine(to: CGPoint(x: 38.04, y: 31))
bezierPath.addCurve(to: CGPoint(x: 39, y: 29.95), controlPoint1: CGPoint(x: 38.54, y: 31), controlPoint2: CGPoint(x: 39, y: 30.77))
bezierPath.addCurve(to: CGPoint(x: 35.39, y: 24.64), controlPoint1: CGPoint(x: 39, y: 28.87), controlPoint2: CGPoint(x: 37.57, y: 26.93))
bezierPath.addCurve(to: CGPoint(x: 32.57, y: 21.59), controlPoint1: CGPoint(x: 34.49, y: 23.46), controlPoint2: CGPoint(x: 33.03, y: 22.18))
bezierPath.addCurve(to: CGPoint(x: 32.57, y: 19.79), controlPoint1: CGPoint(x: 31.9, y: 20.9), controlPoint2: CGPoint(x: 32.1, y: 20.51))
bezierPath.close()
bezierPath.usesEvenOddFillRule = true
fillColor.setFill()
bezierPath.fill()
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)) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()!
//// Resize to Target Frame
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 40, height: 40), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 40, y: resizedFrame.height / 40)
//// Bezier Drawing
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 28.52, y: 22.97))
bezierPath.addCurve(to: CGPoint(x: 25.54, y: 21.56), controlPoint1: CGPoint(x: 28.08, y: 22.76), controlPoint2: CGPoint(x: 25.94, y: 21.71))
bezierPath.addCurve(to: CGPoint(x: 24.56, y: 21.78), controlPoint1: CGPoint(x: 25.14, y: 21.42), controlPoint2: CGPoint(x: 24.85, y: 21.35))
bezierPath.addCurve(to: CGPoint(x: 23.18, y: 23.48), controlPoint1: CGPoint(x: 24.27, y: 22.21), controlPoint2: CGPoint(x: 23.44, y: 23.19))
bezierPath.addCurve(to: CGPoint(x: 22.24, y: 23.59), controlPoint1: CGPoint(x: 22.93, y: 23.77), controlPoint2: CGPoint(x: 22.68, y: 23.81))
bezierPath.addCurve(to: CGPoint(x: 18.74, y: 21.44), controlPoint1: CGPoint(x: 21.81, y: 23.37), controlPoint2: CGPoint(x: 20.4, y: 22.91))
bezierPath.addCurve(to: CGPoint(x: 16.32, y: 18.44), controlPoint1: CGPoint(x: 17.44, y: 20.29), controlPoint2: CGPoint(x: 16.57, y: 18.87))
bezierPath.addCurve(to: CGPoint(x: 16.51, y: 17.55), controlPoint1: CGPoint(x: 16.06, y: 18), controlPoint2: CGPoint(x: 16.29, y: 17.77))
bezierPath.addCurve(to: CGPoint(x: 17.16, y: 16.79), controlPoint1: CGPoint(x: 16.7, y: 17.36), controlPoint2: CGPoint(x: 16.94, y: 17.05))
bezierPath.addCurve(to: CGPoint(x: 17.6, y: 16.07), controlPoint1: CGPoint(x: 17.38, y: 16.54), controlPoint2: CGPoint(x: 17.45, y: 16.36))
bezierPath.addCurve(to: CGPoint(x: 17.56, y: 15.31), controlPoint1: CGPoint(x: 17.74, y: 15.78), controlPoint2: CGPoint(x: 17.67, y: 15.53))
bezierPath.addCurve(to: CGPoint(x: 16.22, y: 12.09), controlPoint1: CGPoint(x: 17.45, y: 15.09), controlPoint2: CGPoint(x: 16.58, y: 12.96))
bezierPath.addCurve(to: CGPoint(x: 15.24, y: 11.35), controlPoint1: CGPoint(x: 15.86, y: 11.25), controlPoint2: CGPoint(x: 15.51, y: 11.36))
bezierPath.addCurve(to: CGPoint(x: 14.4, y: 11.33), controlPoint1: CGPoint(x: 14.98, y: 11.34), controlPoint2: CGPoint(x: 14.69, y: 11.33))
bezierPath.addCurve(to: CGPoint(x: 13.24, y: 11.88), controlPoint1: CGPoint(x: 14.11, y: 11.33), controlPoint2: CGPoint(x: 13.64, y: 11.44))
bezierPath.addCurve(to: CGPoint(x: 11.72, y: 15.49), controlPoint1: CGPoint(x: 12.84, y: 12.31), controlPoint2: CGPoint(x: 11.72, y: 13.36))
bezierPath.addCurve(to: CGPoint(x: 13.5, y: 19.97), controlPoint1: CGPoint(x: 11.72, y: 17.62), controlPoint2: CGPoint(x: 13.28, y: 19.68))
bezierPath.addCurve(to: CGPoint(x: 20.93, y: 26.52), controlPoint1: CGPoint(x: 13.71, y: 20.26), controlPoint2: CGPoint(x: 16.57, y: 24.64))
bezierPath.addCurve(to: CGPoint(x: 23.42, y: 27.43), controlPoint1: CGPoint(x: 21.97, y: 26.96), controlPoint2: CGPoint(x: 22.78, y: 27.23))
bezierPath.addCurve(to: CGPoint(x: 26.16, y: 27.6), controlPoint1: CGPoint(x: 24.46, y: 27.76), controlPoint2: CGPoint(x: 25.41, y: 27.71))
bezierPath.addCurve(to: CGPoint(x: 29.1, y: 25.54), controlPoint1: CGPoint(x: 27, y: 27.48), controlPoint2: CGPoint(x: 28.74, y: 26.55))
bezierPath.addCurve(to: CGPoint(x: 29.35, y: 23.48), controlPoint1: CGPoint(x: 29.46, y: 24.53), controlPoint2: CGPoint(x: 29.46, y: 23.66))
bezierPath.addCurve(to: CGPoint(x: 28.52, y: 22.97), controlPoint1: CGPoint(x: 29.24, y: 23.3), controlPoint2: CGPoint(x: 28.95, y: 23.19))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 20.57, y: 33.77))
bezierPath.addLine(to: CGPoint(x: 20.57, y: 33.77))
bezierPath.addCurve(to: CGPoint(x: 13.2, y: 31.76), controlPoint1: CGPoint(x: 17.97, y: 33.77), controlPoint2: CGPoint(x: 15.42, y: 33.07))
bezierPath.addLine(to: CGPoint(x: 12.67, y: 31.45))
bezierPath.addLine(to: CGPoint(x: 7.18, y: 32.88))
bezierPath.addLine(to: CGPoint(x: 8.65, y: 27.56))
bezierPath.addLine(to: CGPoint(x: 8.3, y: 27.01))
bezierPath.addCurve(to: CGPoint(x: 6.09, y: 19.34), controlPoint1: CGPoint(x: 6.85, y: 24.72), controlPoint2: CGPoint(x: 6.09, y: 22.07))
bezierPath.addCurve(to: CGPoint(x: 20.58, y: 4.93), controlPoint1: CGPoint(x: 6.09, y: 11.4), controlPoint2: CGPoint(x: 12.59, y: 4.93))
bezierPath.addCurve(to: CGPoint(x: 30.82, y: 9.16), controlPoint1: CGPoint(x: 24.45, y: 4.93), controlPoint2: CGPoint(x: 28.08, y: 6.43))
bezierPath.addCurve(to: CGPoint(x: 35.06, y: 19.35), controlPoint1: CGPoint(x: 33.55, y: 11.88), controlPoint2: CGPoint(x: 35.06, y: 15.5))
bezierPath.addCurve(to: CGPoint(x: 20.57, y: 33.77), controlPoint1: CGPoint(x: 35.05, y: 27.3), controlPoint2: CGPoint(x: 28.56, y: 33.77))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 32.9, y: 7.09))
bezierPath.addCurve(to: CGPoint(x: 20.57, y: 2), controlPoint1: CGPoint(x: 29.61, y: 3.81), controlPoint2: CGPoint(x: 25.23, y: 2))
bezierPath.addCurve(to: CGPoint(x: 3.15, y: 19.34), controlPoint1: CGPoint(x: 10.97, y: 2), controlPoint2: CGPoint(x: 3.15, y: 9.78))
bezierPath.addCurve(to: CGPoint(x: 5.47, y: 28.01), controlPoint1: CGPoint(x: 3.14, y: 22.4), controlPoint2: CGPoint(x: 3.95, y: 25.38))
bezierPath.addLine(to: CGPoint(x: 3, y: 37))
bezierPath.addLine(to: CGPoint(x: 12.24, y: 34.59))
bezierPath.addCurve(to: CGPoint(x: 20.57, y: 36.7), controlPoint1: CGPoint(x: 14.78, y: 35.97), controlPoint2: CGPoint(x: 17.65, y: 36.7))
bezierPath.addLine(to: CGPoint(x: 20.57, y: 36.7))
bezierPath.addLine(to: CGPoint(x: 20.57, y: 36.7))
bezierPath.addCurve(to: CGPoint(x: 38, y: 19.36), controlPoint1: CGPoint(x: 30.18, y: 36.7), controlPoint2: CGPoint(x: 38, y: 28.92))
bezierPath.addCurve(to: CGPoint(x: 32.9, y: 7.09), controlPoint1: CGPoint(x: 38, y: 14.72), controlPoint2: CGPoint(x: 36.19, y: 10.36))
bezierPath.close()
bezierPath.usesEvenOddFillRule = true
fillColor.setFill()
bezierPath.fill()
context.restoreGState()
}
@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)) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()!
//// Resize to Target Frame
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 40, height: 40), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 40, y: resizedFrame.height / 40)
//// Bezier Drawing
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 35.15, y: 4.5))
bezierPath.addCurve(to: CGPoint(x: 33.94, y: 4.79), controlPoint1: CGPoint(x: 34.74, y: 4.51), controlPoint2: CGPoint(x: 34.32, y: 4.62))
bezierPath.addCurve(to: CGPoint(x: 18.74, y: 10.7), controlPoint1: CGPoint(x: 33.25, y: 5.06), controlPoint2: CGPoint(x: 26, y: 7.88))
bezierPath.addCurve(to: CGPoint(x: 8.69, y: 14.6), controlPoint1: CGPoint(x: 15.11, y: 12.12), controlPoint2: CGPoint(x: 11.49, y: 13.52))
bezierPath.addCurve(to: CGPoint(x: 3.67, y: 16.54), controlPoint1: CGPoint(x: 5.9, y: 15.69), controlPoint2: CGPoint(x: 3.87, y: 16.47))
bezierPath.addCurve(to: CGPoint(x: 1.4, y: 17.91), controlPoint1: CGPoint(x: 3.01, y: 16.77), controlPoint2: CGPoint(x: 2.05, y: 17.15))
bezierPath.addCurve(to: CGPoint(x: 1.12, y: 19.55), controlPoint1: CGPoint(x: 1.08, y: 18.28), controlPoint2: CGPoint(x: 0.84, y: 18.97))
bezierPath.addCurve(to: CGPoint(x: 2.72, y: 20.75), controlPoint1: CGPoint(x: 1.41, y: 20.13), controlPoint2: CGPoint(x: 1.95, y: 20.46))
bezierPath.addLine(to: CGPoint(x: 2.74, y: 20.76))
bezierPath.addLine(to: CGPoint(x: 2.76, y: 20.77))
bezierPath.addCurve(to: CGPoint(x: 10.35, y: 23.15), controlPoint1: CGPoint(x: 5.56, y: 21.64), controlPoint2: CGPoint(x: 9.78, y: 22.97))
bezierPath.addCurve(to: CGPoint(x: 13.25, y: 32.72), controlPoint1: CGPoint(x: 10.5, y: 23.63), controlPoint2: CGPoint(x: 12.28, y: 29.53))
bezierPath.addCurve(to: CGPoint(x: 14.93, y: 33.73), controlPoint1: CGPoint(x: 13.5, y: 33.38), controlPoint2: CGPoint(x: 14.24, y: 33.82))
bezierPath.addCurve(to: CGPoint(x: 15.53, y: 33.67), controlPoint1: CGPoint(x: 15.05, y: 33.73), controlPoint2: CGPoint(x: 15.28, y: 33.73))
bezierPath.addCurve(to: CGPoint(x: 16.83, y: 32.94), controlPoint1: CGPoint(x: 15.9, y: 33.58), controlPoint2: CGPoint(x: 16.38, y: 33.37))
bezierPath.addLine(to: CGPoint(x: 16.83, y: 32.94))
bezierPath.addCurve(to: CGPoint(x: 20.46, y: 29.4), controlPoint1: CGPoint(x: 17.48, y: 32.33), controlPoint2: CGPoint(x: 19.78, y: 30.07))
bezierPath.addLine(to: CGPoint(x: 27.96, y: 34.96))
bezierPath.addLine(to: CGPoint(x: 27.99, y: 34.98))
bezierPath.addCurve(to: CGPoint(x: 29.57, y: 35.49), controlPoint1: CGPoint(x: 27.99, y: 34.98), controlPoint2: CGPoint(x: 28.67, y: 35.43))
bezierPath.addCurve(to: CGPoint(x: 31.04, y: 35.09), controlPoint1: CGPoint(x: 30.01, y: 35.52), controlPoint2: CGPoint(x: 30.56, y: 35.44))
bezierPath.addCurve(to: CGPoint(x: 32.02, y: 33.42), controlPoint1: CGPoint(x: 31.51, y: 34.75), controlPoint2: CGPoint(x: 31.85, y: 34.17))
bezierPath.addCurve(to: CGPoint(x: 37.28, y: 8.61), controlPoint1: CGPoint(x: 32.61, y: 30.86), controlPoint2: CGPoint(x: 36.6, y: 11.79))
bezierPath.addCurve(to: CGPoint(x: 36.71, y: 5.03), controlPoint1: CGPoint(x: 37.71, y: 6.99), controlPoint2: CGPoint(x: 37.51, y: 5.73))
bezierPath.addCurve(to: CGPoint(x: 35.33, y: 4.5), controlPoint1: CGPoint(x: 36.3, y: 4.68), controlPoint2: CGPoint(x: 35.82, y: 4.52))
bezierPath.addCurve(to: CGPoint(x: 35.15, y: 4.5), controlPoint1: CGPoint(x: 35.27, y: 4.5), controlPoint2: CGPoint(x: 35.21, y: 4.5))
bezierPath.addLine(to: CGPoint(x: 35.15, y: 4.5))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 35.24, y: 6.07))
bezierPath.addCurve(to: CGPoint(x: 35.68, y: 6.21), controlPoint1: CGPoint(x: 35.43, y: 6.07), controlPoint2: CGPoint(x: 35.59, y: 6.12))
bezierPath.addCurve(to: CGPoint(x: 35.77, y: 8.23), controlPoint1: CGPoint(x: 35.88, y: 6.38), controlPoint2: CGPoint(x: 36.13, y: 6.89))
bezierPath.addCurve(to: CGPoint(x: 30.51, y: 33.07), controlPoint1: CGPoint(x: 33.84, y: 16.86), controlPoint2: CGPoint(x: 32.16, y: 25.68))
bezierPath.addCurve(to: CGPoint(x: 30.12, y: 33.83), controlPoint1: CGPoint(x: 30.4, y: 33.57), controlPoint2: CGPoint(x: 30.24, y: 33.75))
bezierPath.addCurve(to: CGPoint(x: 29.67, y: 33.94), controlPoint1: CGPoint(x: 30, y: 33.92), controlPoint2: CGPoint(x: 29.87, y: 33.95))
bezierPath.addCurve(to: CGPoint(x: 28.82, y: 33.66), controlPoint1: CGPoint(x: 29.28, y: 33.91), controlPoint2: CGPoint(x: 28.84, y: 33.67))
bezierPath.addLine(to: CGPoint(x: 16.66, y: 24.63))
bezierPath.addCurve(to: CGPoint(x: 30.24, y: 11.88), controlPoint1: CGPoint(x: 17.86, y: 23.5), controlPoint2: CGPoint(x: 25.86, y: 15.9))
bezierPath.addCurve(to: CGPoint(x: 29.69, y: 10.6), controlPoint1: CGPoint(x: 30.72, y: 11.42), controlPoint2: CGPoint(x: 30.33, y: 10.59))
bezierPath.addCurve(to: CGPoint(x: 27.45, y: 11.72), controlPoint1: CGPoint(x: 28.87, y: 10.76), controlPoint2: CGPoint(x: 28.19, y: 11.35))
bezierPath.addCurve(to: CGPoint(x: 10.91, y: 21.68), controlPoint1: CGPoint(x: 22.04, y: 14.9), controlPoint2: CGPoint(x: 11.81, y: 21.13))
bezierPath.addCurve(to: CGPoint(x: 3.26, y: 19.29), controlPoint1: CGPoint(x: 10.44, y: 21.53), controlPoint2: CGPoint(x: 6.11, y: 20.17))
bezierPath.addCurve(to: CGPoint(x: 2.59, y: 18.92), controlPoint1: CGPoint(x: 2.81, y: 19.11), controlPoint2: CGPoint(x: 2.65, y: 18.97))
bezierPath.addCurve(to: CGPoint(x: 2.59, y: 18.92), controlPoint1: CGPoint(x: 2.59, y: 18.92), controlPoint2: CGPoint(x: 2.59, y: 18.92))
bezierPath.addCurve(to: CGPoint(x: 4.19, y: 18.01), controlPoint1: CGPoint(x: 2.8, y: 18.67), controlPoint2: CGPoint(x: 3.67, y: 18.19))
bezierPath.addCurve(to: CGPoint(x: 9.25, y: 16.06), controlPoint1: CGPoint(x: 4.57, y: 17.87), controlPoint2: CGPoint(x: 6.46, y: 17.14))
bezierPath.addCurve(to: CGPoint(x: 19.3, y: 12.16), controlPoint1: CGPoint(x: 12.05, y: 14.98), controlPoint2: CGPoint(x: 15.67, y: 13.57))
bezierPath.addCurve(to: CGPoint(x: 34.56, y: 6.23), controlPoint1: CGPoint(x: 24.39, y: 10.19), controlPoint2: CGPoint(x: 29.48, y: 8.21))
bezierPath.addCurve(to: CGPoint(x: 35.24, y: 6.07), controlPoint1: CGPoint(x: 34.81, y: 6.11), controlPoint2: CGPoint(x: 35.04, y: 6.06))
bezierPath.addLine(to: CGPoint(x: 35.24, y: 6.07))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 16.15, y: 26.2))
bezierPath.addLine(to: CGPoint(x: 19.19, y: 28.45))
bezierPath.addCurve(to: CGPoint(x: 15.79, y: 31.79), controlPoint1: CGPoint(x: 18.35, y: 29.29), controlPoint2: CGPoint(x: 16.35, y: 31.26))
bezierPath.addLine(to: CGPoint(x: 16.15, y: 26.2))
bezierPath.addLine(to: CGPoint(x: 16.15, y: 26.2))
bezierPath.close()
fillColor.setFill()
bezierPath.fill()
context.restoreGState()
}
@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)) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()!
//// Resize to Target Frame
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 41, height: 40), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 41, y: resizedFrame.height / 40)
//// Bezier Drawing
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 22.36, y: 21.49))
bezierPath.addLine(to: CGPoint(x: 27.52, y: 21.49))
bezierPath.addLine(to: CGPoint(x: 28.29, y: 15.45))
bezierPath.addLine(to: CGPoint(x: 22.36, y: 15.45))
bezierPath.addLine(to: CGPoint(x: 22.36, y: 11.59))
bezierPath.addCurve(to: CGPoint(x: 25.33, y: 8.65), controlPoint1: CGPoint(x: 22.36, y: 9.84), controlPoint2: CGPoint(x: 22.84, y: 8.65))
bezierPath.addLine(to: CGPoint(x: 28.5, y: 8.64))
bezierPath.addLine(to: CGPoint(x: 28.5, y: 3.24))
bezierPath.addCurve(to: CGPoint(x: 23.88, y: 3), controlPoint1: CGPoint(x: 27.95, y: 3.16), controlPoint2: CGPoint(x: 26.07, y: 3))
bezierPath.addCurve(to: CGPoint(x: 16.17, y: 10.99), controlPoint1: CGPoint(x: 19.3, y: 3), controlPoint2: CGPoint(x: 16.17, y: 5.82))
bezierPath.addLine(to: CGPoint(x: 16.17, y: 15.45))
bezierPath.addLine(to: CGPoint(x: 11, y: 15.45))
bezierPath.addLine(to: CGPoint(x: 11, y: 21.49))
bezierPath.addLine(to: CGPoint(x: 16.17, y: 21.49))
bezierPath.addLine(to: CGPoint(x: 16.17, y: 37))
bezierPath.addLine(to: CGPoint(x: 22.36, y: 37))
bezierPath.addLine(to: CGPoint(x: 22.36, y: 21.49))
bezierPath.close()
bezierPath.usesEvenOddFillRule = true
fillColor.setFill()
bezierPath.fill()
context.restoreGState()
}
@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)) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()!
//// Resize to Target Frame
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 41, height: 40), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 41, y: resizedFrame.height / 40)
//// Bezier 5 Drawing
let bezier5Path = UIBezierPath()
bezier5Path.move(to: CGPoint(x: 34, y: 16.95))
bezier5Path.addCurve(to: CGPoint(x: 23.83, y: 6), controlPoint1: CGPoint(x: 34.05, y: 11.59), controlPoint2: CGPoint(x: 29.49, y: 6.68))
bezier5Path.addCurve(to: CGPoint(x: 23.47, y: 5.94), controlPoint1: CGPoint(x: 23.72, y: 5.98), controlPoint2: CGPoint(x: 23.6, y: 5.96))
bezier5Path.addCurve(to: CGPoint(x: 22.61, y: 5.85), controlPoint1: CGPoint(x: 23.19, y: 5.9), controlPoint2: CGPoint(x: 22.9, y: 5.85))
bezier5Path.addCurve(to: CGPoint(x: 21.06, y: 7.15), controlPoint1: CGPoint(x: 21.45, y: 5.85), controlPoint2: CGPoint(x: 21.14, y: 6.66))
bezier5Path.addCurve(to: CGPoint(x: 21.28, y: 8.33), controlPoint1: CGPoint(x: 20.98, y: 7.62), controlPoint2: CGPoint(x: 21.05, y: 8.02))
bezier5Path.addCurve(to: CGPoint(x: 22.88, y: 9.02), controlPoint1: CGPoint(x: 21.67, y: 8.85), controlPoint2: CGPoint(x: 22.34, y: 8.94))
bezier5Path.addCurve(to: CGPoint(x: 23.32, y: 9.09), controlPoint1: CGPoint(x: 23.04, y: 9.04), controlPoint2: CGPoint(x: 23.19, y: 9.06))
bezier5Path.addCurve(to: CGPoint(x: 30.94, y: 17.04), controlPoint1: CGPoint(x: 28.4, y: 10.22), controlPoint2: CGPoint(x: 30.11, y: 12.01))
bezier5Path.addCurve(to: CGPoint(x: 30.98, y: 17.48), controlPoint1: CGPoint(x: 30.96, y: 17.17), controlPoint2: CGPoint(x: 30.97, y: 17.32))
bezier5Path.addCurve(to: CGPoint(x: 32.44, y: 19.33), controlPoint1: CGPoint(x: 31.02, y: 18.08), controlPoint2: CGPoint(x: 31.1, y: 19.33))
bezier5Path.addLine(to: CGPoint(x: 32.44, y: 19.33))
bezier5Path.addCurve(to: CGPoint(x: 32.8, y: 19.3), controlPoint1: CGPoint(x: 32.55, y: 19.33), controlPoint2: CGPoint(x: 32.68, y: 19.32))
bezier5Path.addCurve(to: CGPoint(x: 34, y: 17.42), controlPoint1: CGPoint(x: 34.05, y: 19.11), controlPoint2: CGPoint(x: 34.02, y: 17.97))
bezier5Path.addCurve(to: CGPoint(x: 34, y: 17.02), controlPoint1: CGPoint(x: 33.99, y: 17.26), controlPoint2: CGPoint(x: 33.99, y: 17.12))
bezier5Path.addCurve(to: CGPoint(x: 34, y: 16.95), controlPoint1: CGPoint(x: 34, y: 17), controlPoint2: CGPoint(x: 34, y: 16.97))
bezier5Path.close()
bezier5Path.move(to: CGPoint(x: 22.28, y: 4.03))
bezier5Path.addCurve(to: CGPoint(x: 22.69, y: 4.07), controlPoint1: CGPoint(x: 22.43, y: 4.04), controlPoint2: CGPoint(x: 22.57, y: 4.05))
bezier5Path.addCurve(to: CGPoint(x: 35.93, y: 17.71), controlPoint1: CGPoint(x: 31.03, y: 5.35), controlPoint2: CGPoint(x: 34.87, y: 9.31))
bezier5Path.addCurve(to: CGPoint(x: 35.95, y: 18.22), controlPoint1: CGPoint(x: 35.95, y: 17.86), controlPoint2: CGPoint(x: 35.95, y: 18.03))
bezier5Path.addCurve(to: CGPoint(x: 37.45, y: 20.27), controlPoint1: CGPoint(x: 35.97, y: 18.87), controlPoint2: CGPoint(x: 35.99, y: 20.24))
bezier5Path.addLine(to: CGPoint(x: 37.5, y: 20.27))
bezier5Path.addCurve(to: CGPoint(x: 38.59, y: 19.85), controlPoint1: CGPoint(x: 37.96, y: 20.27), controlPoint2: CGPoint(x: 38.33, y: 20.13))
bezier5Path.addCurve(to: CGPoint(x: 38.99, y: 18.1), controlPoint1: CGPoint(x: 39.04, y: 19.38), controlPoint2: CGPoint(x: 39.01, y: 18.67))
bezier5Path.addCurve(to: CGPoint(x: 38.98, y: 17.71), controlPoint1: CGPoint(x: 38.98, y: 17.96), controlPoint2: CGPoint(x: 38.98, y: 17.82))
bezier5Path.addCurve(to: CGPoint(x: 23.05, y: 1.02), controlPoint1: CGPoint(x: 39.08, y: 9.11), controlPoint2: CGPoint(x: 31.64, y: 1.31))
bezier5Path.addCurve(to: CGPoint(x: 22.95, y: 1.03), controlPoint1: CGPoint(x: 23.01, y: 1.02), controlPoint2: CGPoint(x: 22.98, y: 1.02))
bezier5Path.addCurve(to: CGPoint(x: 22.84, y: 1.03), controlPoint1: CGPoint(x: 22.93, y: 1.03), controlPoint2: CGPoint(x: 22.9, y: 1.03))
bezier5Path.addCurve(to: CGPoint(x: 22.54, y: 1.02), controlPoint1: CGPoint(x: 22.76, y: 1.03), controlPoint2: CGPoint(x: 22.65, y: 1.03))
bezier5Path.addCurve(to: CGPoint(x: 22.1, y: 1), controlPoint1: CGPoint(x: 22.41, y: 1.01), controlPoint2: CGPoint(x: 22.25, y: 1))
bezier5Path.addCurve(to: CGPoint(x: 20.44, y: 2.55), controlPoint1: CGPoint(x: 20.73, y: 1), controlPoint2: CGPoint(x: 20.47, y: 1.97))
bezier5Path.addCurve(to: CGPoint(x: 22.28, y: 4.03), controlPoint1: CGPoint(x: 20.36, y: 3.89), controlPoint2: CGPoint(x: 21.66, y: 3.99))
bezier5Path.close()
bezier5Path.move(to: CGPoint(x: 35.53, y: 28.58))
bezier5Path.addCurve(to: CGPoint(x: 35, y: 28.17), controlPoint1: CGPoint(x: 35.35, y: 28.44), controlPoint2: CGPoint(x: 35.17, y: 28.3))
bezier5Path.addCurve(to: CGPoint(x: 32.18, y: 26.1), controlPoint1: CGPoint(x: 34.09, y: 27.43), controlPoint2: CGPoint(x: 33.12, y: 26.76))
bezier5Path.addCurve(to: CGPoint(x: 31.6, y: 25.7), controlPoint1: CGPoint(x: 31.98, y: 25.97), controlPoint2: CGPoint(x: 31.79, y: 25.83))
bezier5Path.addCurve(to: CGPoint(x: 28.3, y: 24.44), controlPoint1: CGPoint(x: 30.4, y: 24.85), controlPoint2: CGPoint(x: 29.32, y: 24.44))
bezier5Path.addCurve(to: CGPoint(x: 24.74, y: 26.7), controlPoint1: CGPoint(x: 26.92, y: 24.44), controlPoint2: CGPoint(x: 25.73, y: 25.2))
bezier5Path.addCurve(to: CGPoint(x: 23.12, y: 27.68), controlPoint1: CGPoint(x: 24.3, y: 27.36), controlPoint2: CGPoint(x: 23.77, y: 27.68))
bezier5Path.addCurve(to: CGPoint(x: 21.81, y: 27.36), controlPoint1: CGPoint(x: 22.73, y: 27.68), controlPoint2: CGPoint(x: 22.29, y: 27.57))
bezier5Path.addCurve(to: CGPoint(x: 13.59, y: 19.35), controlPoint1: CGPoint(x: 17.93, y: 25.6), controlPoint2: CGPoint(x: 15.17, y: 22.91))
bezier5Path.addCurve(to: CGPoint(x: 14.42, y: 15.59), controlPoint1: CGPoint(x: 12.82, y: 17.63), controlPoint2: CGPoint(x: 13.07, y: 16.51))
bezier5Path.addCurve(to: CGPoint(x: 16.5, y: 12.26), controlPoint1: CGPoint(x: 15.18, y: 15.07), controlPoint2: CGPoint(x: 16.6, y: 14.11))
bezier5Path.addCurve(to: CGPoint(x: 9.8, y: 3.12), controlPoint1: CGPoint(x: 16.39, y: 10.16), controlPoint2: CGPoint(x: 11.75, y: 3.84))
bezier5Path.addCurve(to: CGPoint(x: 7.22, y: 3.11), controlPoint1: CGPoint(x: 8.97, y: 2.82), controlPoint2: CGPoint(x: 8.11, y: 2.81))
bezier5Path.addCurve(to: CGPoint(x: 2.56, y: 6.95), controlPoint1: CGPoint(x: 4.97, y: 3.87), controlPoint2: CGPoint(x: 3.36, y: 5.19))
bezier5Path.addCurve(to: CGPoint(x: 2.66, y: 12.7), controlPoint1: CGPoint(x: 1.78, y: 8.64), controlPoint2: CGPoint(x: 1.82, y: 10.63))
bezier5Path.addCurve(to: CGPoint(x: 12.84, y: 28.19), controlPoint1: CGPoint(x: 5.09, y: 18.67), controlPoint2: CGPoint(x: 8.52, y: 23.89))
bezier5Path.addCurve(to: CGPoint(x: 28.27, y: 38.44), controlPoint1: CGPoint(x: 17.06, y: 32.4), controlPoint2: CGPoint(x: 22.26, y: 35.85))
bezier5Path.addCurve(to: CGPoint(x: 29.8, y: 38.9), controlPoint1: CGPoint(x: 28.82, y: 38.68), controlPoint2: CGPoint(x: 29.39, y: 38.8))
bezier5Path.addCurve(to: CGPoint(x: 30.15, y: 38.98), controlPoint1: CGPoint(x: 29.94, y: 38.93), controlPoint2: CGPoint(x: 30.07, y: 38.95))
bezier5Path.addCurve(to: CGPoint(x: 30.3, y: 39), controlPoint1: CGPoint(x: 30.2, y: 38.99), controlPoint2: CGPoint(x: 30.25, y: 39))
bezier5Path.addLine(to: CGPoint(x: 30.35, y: 39))
bezier5Path.addLine(to: CGPoint(x: 30.35, y: 39))
bezier5Path.addCurve(to: CGPoint(x: 37.63, y: 33.46), controlPoint1: CGPoint(x: 33.18, y: 39), controlPoint2: CGPoint(x: 36.58, y: 36.41))
bezier5Path.addCurve(to: CGPoint(x: 35.53, y: 28.58), controlPoint1: CGPoint(x: 38.54, y: 30.88), controlPoint2: CGPoint(x: 36.87, y: 29.61))
bezier5Path.close()
bezier5Path.move(to: CGPoint(x: 23.53, y: 10.86))
bezier5Path.addCurve(to: CGPoint(x: 21.68, y: 11.93), controlPoint1: CGPoint(x: 23.05, y: 10.87), controlPoint2: CGPoint(x: 22.04, y: 10.9))
bezier5Path.addCurve(to: CGPoint(x: 21.74, y: 13.16), controlPoint1: CGPoint(x: 21.52, y: 12.41), controlPoint2: CGPoint(x: 21.54, y: 12.82))
bezier5Path.addCurve(to: CGPoint(x: 23.14, y: 13.9), controlPoint1: CGPoint(x: 22.04, y: 13.67), controlPoint2: CGPoint(x: 22.62, y: 13.82))
bezier5Path.addCurve(to: CGPoint(x: 26.21, y: 17.21), controlPoint1: CGPoint(x: 25.04, y: 14.21), controlPoint2: CGPoint(x: 26.02, y: 15.26))
bezier5Path.addCurve(to: CGPoint(x: 27.7, y: 18.75), controlPoint1: CGPoint(x: 26.3, y: 18.12), controlPoint2: CGPoint(x: 26.91, y: 18.75))
bezier5Path.addLine(to: CGPoint(x: 27.7, y: 18.75))
bezier5Path.addCurve(to: CGPoint(x: 27.88, y: 18.74), controlPoint1: CGPoint(x: 27.76, y: 18.75), controlPoint2: CGPoint(x: 27.82, y: 18.75))
bezier5Path.addCurve(to: CGPoint(x: 29.24, y: 16.68), controlPoint1: CGPoint(x: 28.82, y: 18.63), controlPoint2: CGPoint(x: 29.28, y: 17.94))
bezier5Path.addCurve(to: CGPoint(x: 27.4, y: 12.69), controlPoint1: CGPoint(x: 29.25, y: 15.37), controlPoint2: CGPoint(x: 28.57, y: 13.88))
bezier5Path.addCurve(to: CGPoint(x: 23.53, y: 10.86), controlPoint1: CGPoint(x: 26.23, y: 11.5), controlPoint2: CGPoint(x: 24.82, y: 10.83))
bezier5Path.close()
fillColor.setFill()
bezier5Path.fill()
context.restoreGState()
}
@objc(SocialIconStyleKitResizingBehavior)
public 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 {
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() {}
}
}
@@ -0,0 +1,235 @@
// 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 SPCodeDraw {
public 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 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)) {
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 bezier2Path = UIBezierPath()
bezier2Path.move(to: CGPoint(x: 49.5, y: 8.22))
bezier2Path.addLine(to: CGPoint(x: 39.17, y: 40.29))
bezier2Path.addLine(to: CGPoint(x: 36.66, y: 40.32))
bezier2Path.addLine(to: CGPoint(x: 6.89, y: 40.68))
bezier2Path.addLine(to: CGPoint(x: 32.78, y: 60.88))
bezier2Path.addLine(to: CGPoint(x: 32.04, y: 63.39))
bezier2Path.addLine(to: CGPoint(x: 23.17, y: 93.18))
bezier2Path.addLine(to: CGPoint(x: 49.5, y: 73.61))
bezier2Path.addLine(to: CGPoint(x: 51.55, y: 75.13))
bezier2Path.addLine(to: CGPoint(x: 75.83, y: 93.19))
bezier2Path.addLine(to: CGPoint(x: 66.22, y: 60.89))
bezier2Path.addLine(to: CGPoint(x: 68.23, y: 59.32))
bezier2Path.addLine(to: CGPoint(x: 92.11, y: 40.68))
bezier2Path.addLine(to: CGPoint(x: 59.83, y: 40.29))
bezier2Path.addLine(to: CGPoint(x: 49.5, y: 8.22))
bezier2Path.close()
bezier2Path.move(to: CGPoint(x: 52.21, y: 5.04))
bezier2Path.addLine(to: CGPoint(x: 62.38, y: 36.61))
bezier2Path.addLine(to: CGPoint(x: 94.17, y: 37))
bezier2Path.addCurve(to: CGPoint(x: 95.84, y: 42.39), controlPoint1: CGPoint(x: 96.9, y: 37.03), controlPoint2: CGPoint(x: 98.04, y: 40.68))
bezier2Path.addLine(to: CGPoint(x: 70.34, y: 62.29))
bezier2Path.addLine(to: CGPoint(x: 79.81, y: 94.1))
bezier2Path.addCurve(to: CGPoint(x: 75.43, y: 97.43), controlPoint1: CGPoint(x: 80.63, y: 96.84), controlPoint2: CGPoint(x: 77.67, y: 99.09))
bezier2Path.addLine(to: CGPoint(x: 49.5, y: 78.16))
bezier2Path.addLine(to: CGPoint(x: 23.57, y: 97.43))
bezier2Path.addCurve(to: CGPoint(x: 19.19, y: 94.1), controlPoint1: CGPoint(x: 21.34, y: 99.09), controlPoint2: CGPoint(x: 18.37, y: 96.83))
bezier2Path.addLine(to: CGPoint(x: 28.66, y: 62.29))
bezier2Path.addLine(to: CGPoint(x: 3.16, y: 42.39))
bezier2Path.addCurve(to: CGPoint(x: 4.83, y: 36.99), controlPoint1: CGPoint(x: 0.96, y: 40.68), controlPoint2: CGPoint(x: 2.1, y: 37.03))
bezier2Path.addLine(to: CGPoint(x: 36.62, y: 36.61))
bezier2Path.addLine(to: CGPoint(x: 46.79, y: 5.04))
bezier2Path.addCurve(to: CGPoint(x: 52.21, y: 5.04), controlPoint1: CGPoint(x: 47.67, y: 2.32), controlPoint2: CGPoint(x: 51.33, y: 2.32))
bezier2Path.close()
color.setFill()
bezier2Path.fill()
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)) {
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: 51.78, y: 4.92))
bezierPath.addLine(to: CGPoint(x: 61.96, y: 36.5))
bezierPath.addLine(to: CGPoint(x: 93.75, y: 36.88))
bezierPath.addCurve(to: CGPoint(x: 95.42, y: 42.28), controlPoint1: CGPoint(x: 96.48, y: 36.92), controlPoint2: CGPoint(x: 97.61, y: 40.57))
bezierPath.addLine(to: CGPoint(x: 69.92, y: 62.17))
bezierPath.addLine(to: CGPoint(x: 79.39, y: 93.98))
bezierPath.addCurve(to: CGPoint(x: 75.01, y: 97.32), controlPoint1: CGPoint(x: 80.2, y: 96.72), controlPoint2: CGPoint(x: 77.24, y: 98.98))
bezierPath.addLine(to: CGPoint(x: 49.08, y: 78.04))
bezierPath.addLine(to: CGPoint(x: 23.14, y: 97.32))
bezierPath.addCurve(to: CGPoint(x: 18.76, y: 93.98), controlPoint1: CGPoint(x: 20.91, y: 98.97), controlPoint2: CGPoint(x: 17.95, y: 96.71))
bezierPath.addLine(to: CGPoint(x: 28.24, y: 62.17))
bezierPath.addLine(to: CGPoint(x: 2.73, y: 42.27))
bezierPath.addCurve(to: CGPoint(x: 4.41, y: 36.88), controlPoint1: CGPoint(x: 0.54, y: 40.57), controlPoint2: CGPoint(x: 1.67, y: 36.91))
bezierPath.addLine(to: CGPoint(x: 36.19, y: 36.49))
bezierPath.addLine(to: CGPoint(x: 46.37, y: 4.92))
bezierPath.addCurve(to: CGPoint(x: 51.78, y: 4.92), controlPoint1: CGPoint(x: 47.24, y: 2.2), controlPoint2: CGPoint(x: 50.91, y: 2.2))
bezierPath.close()
color.setFill()
bezierPath.fill()
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)) {
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 clip2Path = UIBezierPath()
clip2Path.move(to: CGPoint(x: 38.59, y: 33.54))
clip2Path.addLine(to: CGPoint(x: 38.59, y: 38.16))
clip2Path.addLine(to: CGPoint(x: 24.9, y: 38.16))
clip2Path.addLine(to: CGPoint(x: 24.9, y: 90.08))
clip2Path.addLine(to: CGPoint(x: 75.1, y: 90.08))
clip2Path.addLine(to: CGPoint(x: 75.1, y: 38.16))
clip2Path.addLine(to: CGPoint(x: 61.41, y: 38.16))
clip2Path.addLine(to: CGPoint(x: 61.41, y: 33.54))
clip2Path.addLine(to: CGPoint(x: 80, y: 33.54))
clip2Path.addLine(to: CGPoint(x: 80, y: 95))
clip2Path.addLine(to: CGPoint(x: 20, y: 95))
clip2Path.addLine(to: CGPoint(x: 20, y: 33.54))
clip2Path.addLine(to: CGPoint(x: 38.59, y: 33.54))
clip2Path.close()
clip2Path.move(to: CGPoint(x: 52.27, y: 61.81))
clip2Path.addLine(to: CGPoint(x: 47.73, y: 61.81))
clip2Path.addLine(to: CGPoint(x: 47.73, y: 14.88))
clip2Path.addLine(to: CGPoint(x: 40.08, y: 22.75))
clip2Path.addLine(to: CGPoint(x: 37.14, y: 19.73))
clip2Path.addLine(to: CGPoint(x: 50, y: 6.5))
clip2Path.addLine(to: CGPoint(x: 62.86, y: 19.73))
clip2Path.addLine(to: CGPoint(x: 59.92, y: 22.75))
clip2Path.addLine(to: CGPoint(x: 52.27, y: 14.88))
clip2Path.addLine(to: CGPoint(x: 52.27, y: 61.81))
clip2Path.close()
color.setFill()
clip2Path.fill()
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)) {
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)
public 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 {
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() {}
}
}
@@ -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
public 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() {}
}
@@ -0,0 +1,29 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
public func delay(_ delay:Double, closure:@escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when) {
closure()
}
}
@@ -0,0 +1,59 @@
// 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 SPDevice {
static var iphone: Bool {
return UIDevice.current.userInterfaceIdiom == .phone
}
static var ipad: Bool {
return UIDevice.current.userInterfaceIdiom == .pad
}
struct Orientation {
static var isPortrait: Bool {
var isPortraitOrientation = true
if UIDevice.current.orientation.isValidInterfaceOrientation {
if UIDevice.current.orientation.isPortrait {
isPortraitOrientation = true
} else {
isPortraitOrientation = false
}
} else {
if UIScreen.main.bounds.width < UIScreen.main.bounds.height {
isPortraitOrientation = true
} else {
isPortraitOrientation = false
}
}
return isPortraitOrientation
}
private init() {}
}
private init() {}
}
@@ -0,0 +1,55 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
struct SPDownloader {
static func image(link: String, withComplection complection: @escaping (UIImage?) -> ()) {
guard let url = URL(string: link) else {
DispatchQueue.main.async {
complection(nil)
}
return
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else {
DispatchQueue.main.async {
complection(nil)
}
return
}
DispatchQueue.main.async {
complection(image)
}
}.resume()
}
private init() {}
}
@@ -0,0 +1,49 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
extension Array {
func get(count: Int) -> Array {
if (count < self.count) { return Array(self[0..<count]) }
return Array(self)
}
}
extension Array where Element: Equatable {
mutating func removeDuplicates() {
var result = [Element]()
for value in self {
if result.contains(value) == false { result.append(value) }
}
self = result
}
}
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] }
return nil
}
}
@@ -0,0 +1,39 @@
// 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 bottomXPosition: CGFloat {
get { return self.origin.x + self.width }
set { self.origin.x = newValue - self.width }
}
var bottomYPosition: CGFloat {
get { return self.origin.y + self.height }
set { self.origin.y = newValue - self.height }
}
var minSideSize: CGFloat {
return min(self.width, self.height)
}
}
@@ -0,0 +1,37 @@
// 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 CGSize {
func resize(width: CGFloat) -> CGSize {
let relativeSideSize = self.width / self.height
let newHeight = width / relativeSideSize
return CGSize.init(width: width, height: newHeight)
}
func resize(height: CGFloat) -> CGSize {
let relativeSideSize = self.width / self.height
let newWidth = height * relativeSideSize
return CGSize.init(width: newWidth, height: height)
}
}
@@ -0,0 +1,38 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
extension Date {
func format(mask: String) -> String {
let dateFormatter = DateFormatter()
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
}
}
@@ -0,0 +1,37 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
extension Strideable {
public mutating func setIfMore(when value: Self) {
if self > value {
self = value
}
}
public mutating func setIfFewer(when value: Self) {
if self < value {
self = value
}
}
}
@@ -0,0 +1,80 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
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))
}
}
mutating func dropFirst(substring: String) {
if self.hasPrefix(substring) {
self = String(dropFirst(substring.count))
}
}
func uppercasedFirstLetter() -> String {
let lowercaseSctring = self.lowercased()
return lowercaseSctring.prefix(1).uppercased() + lowercaseSctring.dropFirst()
}
mutating func uppercaseFirstLetter() {
self = self.uppercasedFirstLetter()
}
func removeAllSpaces() -> String {
return self.components(separatedBy: .whitespaces).joined()
}
mutating func removeAllSpaces() {
self = self.removeAllSpaces()
}
var isEmail: Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailTest = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
return emailTest.evaluate(with: self)
}
var isLink: Bool {
if let url = URL(string: self) {
return UIApplication.shared.canOpenURL(url)
}
return false
}
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)
}
}
@@ -0,0 +1,34 @@
// 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 UITextField {
@IBInspectable var placeholderColor: UIColor? {
get {
return self.placeholderColor
}
set {
self.attributedPlaceholder = NSAttributedString(string:self.placeholder != nil ? self.placeholder! : "", attributes:[NSAttributedString.Key.foregroundColor: newValue!])
}
}
}
@@ -0,0 +1,103 @@
// 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 UIAlertController {
static var elementsColor: UIColor {
get {
return UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor
}
set {
UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = newValue
}
}
public 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)
)
}
ac.addAction(UIAlertAction.init(
title: buttonTitle,
style: UIAlertAction.Style.default,
handler: { (action) in
complection()
}))
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) {
let ac = UIAlertController(
title: title,
message: message,
preferredStyle: .actionSheet
)
var style = UIAlertAction.Style.default
if isDestructive {
style = .destructive
}
ac.addAction(UIAlertAction.init(
title: buttonTitle,
style: style,
handler: { (action) in
complection(true)
}))
ac.addAction(UIAlertAction.init(
title: cancelButtonTitle,
style: UIAlertAction.Style.default,
handler: { (action) in
complection(false)
}))
viewController.present(ac, animated: true, completion: nil)
}
}
extension UIAlertController {
func addAction(title: String, complection: @escaping ()->()) {
let action = UIAlertAction(title: title, style: .default) { (action) in
complection()
}
self.addAction(action)
}
func addDestructiveAction(title: String, complection: @escaping ()->()) {
let action = UIAlertAction(title: title, style: .destructive) { (action) in
complection()
}
self.addAction(action)
}
}
@@ -0,0 +1,128 @@
// 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 UIButton {
typealias UIButtonTargetClosure = () -> ()
private class ClosureWrapper: NSObject {
let closure: UIButtonTargetClosure
init(_ closure: @escaping UIButtonTargetClosure) {
self.closure = closure
}
}
private struct AssociatedKeys {
static var targetClosure = "targetClosure"
}
private var targetClosure: UIButtonTargetClosure? {
get {
guard let closureWrapper = objc_getAssociatedObject(self, &AssociatedKeys.targetClosure) as? ClosureWrapper else { return nil }
return closureWrapper.closure
}
set(newValue) {
guard let newValue = newValue else { return }
objc_setAssociatedObject(self, &AssociatedKeys.targetClosure, ClosureWrapper(newValue), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
func target(_ action: @escaping UIButtonTargetClosure) {
targetClosure = action
addTarget(self, action: #selector(UIButton.targetAction), for: .touchUpInside)
}
@objc func targetAction() {
guard let targetClosure = targetClosure else { return }
targetClosure()
}
}
extension UIButton {
func setTitle(_ title: String) {
self.setTitle(title, for: .normal)
}
func setTitleColor(_ color: UIColor) {
self.setTitleColor(color, for: .normal)
self.setTitleColor(color.withAlphaComponent(0.7), for: .highlighted)
}
func removeAllTargets() {
self.removeTarget(nil, action: nil, for: .allEvents)
}
func showText(_ text: String, withComplection completion: (() -> Void)! = {}) {
let baseText = self.titleLabel?.text ?? " "
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: {
completion()
})
})
})
})
}
func setAnimatableText(_ text: String, withComplection completion: (() -> Void)! = {}) {
SPAnimation.animate(0.3, animations: {
self.titleLabel?.alpha = 0
}, withComplection: {
self.setTitle(text, for: .normal)
SPAnimation.animate(0.3, animations: {
self.titleLabel?.alpha = 1
}, withComplection: {
completion()
})
})
}
func hideContent(completion: (() -> Void)! = {}) {
SPAnimation.animate(0.2, animations: {
self.titleLabel?.alpha = 0
}, withComplection: {
completion()
})
}
func showContent(completion: (() -> Void)! = {}) {
SPAnimation.animate(0.2, animations: {
self.titleLabel?.alpha = 1
}, withComplection: {
completion()
})
}
}
@@ -0,0 +1,32 @@
// 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 UICollectionView {
var currentIndexCellPath: IndexPath? {
let visibleRect = CGRect(origin: self.contentOffset, size: self.bounds.size)
let visiblePoint = CGPoint.init(x: visibleRect.midX, y: visibleRect.midY)
let visibleIndexPath = self.indexPathForItem(at: visiblePoint)
return visibleIndexPath
}
}
@@ -0,0 +1,68 @@
// 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 extension UIColor {
convenience init(hex: String) {
var red: CGFloat = 0.0
var green: CGFloat = 0.0
var blue: CGFloat = 0.0
var alpha: CGFloat = 1.0
var hex: String = hex
if hex.hasPrefix("#") {
let index = hex.index(hex.startIndex, offsetBy: 1)
hex = String(hex[index...])
}
let scanner = Scanner(string: hex)
var hexValue: CUnsignedLongLong = 0
if scanner.scanHexInt64(&hexValue) {
switch (hex.count) {
case 3:
red = CGFloat((hexValue & 0xF00) >> 8) / 15.0
green = CGFloat((hexValue & 0x0F0) >> 4) / 15.0
blue = CGFloat(hexValue & 0x00F) / 15.0
case 4:
red = CGFloat((hexValue & 0xF000) >> 12) / 15.0
green = CGFloat((hexValue & 0x0F00) >> 8) / 15.0
blue = CGFloat((hexValue & 0x00F0) >> 4) / 15.0
alpha = CGFloat(hexValue & 0x000F) / 15.0
case 6:
red = CGFloat((hexValue & 0xFF0000) >> 16) / 255.0
green = CGFloat((hexValue & 0x00FF00) >> 8) / 255.0
blue = CGFloat(hexValue & 0x0000FF) / 255.0
case 8:
red = CGFloat((hexValue & 0xFF000000) >> 24) / 255.0
green = CGFloat((hexValue & 0x00FF0000) >> 16) / 255.0
blue = CGFloat((hexValue & 0x0000FF00) >> 8) / 255.0
alpha = CGFloat(hexValue & 0x000000FF) / 255.0
default:
print("SPUIColorExtension - Invalid RGB string, number of characters after '#' should be either 3, 4, 6 or 8", terminator: "")
}
} else {
print("SPUIColorExtension - Scan hex error")
}
self.init(red:red, green:green, blue:blue, alpha:alpha)
}
}
@@ -0,0 +1,62 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public extension UIFont {
public static func system(type: BoldType, size: CGFloat) -> UIFont {
return UIFont.systemFont(ofSize: size, weight: self.getBoldTypeBy(boldType: type))
}
@available(iOS 8.2, *)
private static func getBoldTypeBy(boldType: BoldType) -> UIFont.Weight {
switch boldType {
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
}
}
public enum BoldType {
case Regular
case Medium
case Light
case UltraLight
case Heavy
case Bold
case DemiBold
case None
}
}
@@ -0,0 +1,35 @@
// 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 extension UIImage {
public 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!
}
}
@@ -0,0 +1,55 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension UIImageView {
public func setNative() {
self.layer.borderWidth = 0.5
self.layer.borderColor = SPNativeColors.midGray.cgColor
self.layer.masksToBounds = true
}
public func downloadedFrom(url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit, withComplection complection: @escaping (UIImage?) -> () = {_ in }) {
DispatchQueue.main.async {
self.contentMode = mode
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { complection(nil); return }
DispatchQueue.main.async() { () -> Void in
self.image = image
complection(image)
}
}.resume()
}
public 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)
}
}
@@ -0,0 +1,77 @@
// 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 extension UILabel {
func setShadowOffsetForLetters(blurRadius: CGFloat = 0, widthOffset: CGFloat = 0, heightOffset: CGFloat, opacity: CGFloat) {
self.layer.shadowRadius = blurRadius
self.layer.shadowOffset = CGSize(
width: widthOffset,
height: heightOffset
)
self.layer.shadowOpacity = Float(opacity)
}
func setShadowOffsetFactorForLetters(blurRadius: CGFloat = 0, widthOffsetFactor: CGFloat = 0, heightOffsetFactor: CGFloat, opacity: CGFloat) {
let widthOffset = widthOffsetFactor * self.frame.width
let heightOffset = heightOffsetFactor * self.frame.height
self.setShadowOffsetForLetters(blurRadius: blurRadius, widthOffset: widthOffset, heightOffset: heightOffset, opacity: opacity)
}
func removeShadowForLetters() {
self.setShadowOffsetForLetters(blurRadius: 0, widthOffset: 0, heightOffset: 0, opacity: 0)
}
func setCenteringAlignment() {
self.textAlignment = .center
self.baselineAdjustment = .alignCenters
}
func setLettersSpacing(_ value: CGFloat) {
if let textString = text {
let attrs: [NSAttributedString.Key : Any] = [.kern: value]
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
}
}
@@ -0,0 +1,38 @@
// 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 UINavigationController {
static var elementsColor: UIColor {
get {
if UINavigationBar.appearance().tintColor != nil {
return UINavigationBar.appearance().tintColor
} else {
return SPNativeColors.blue
}
}
set {
UINavigationBar.appearance().tintColor = newValue
}
}
}
@@ -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) }
}
}
@@ -0,0 +1,59 @@
// 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 UITableView {
var isEmpty: Bool {
return self.lastSectionWithRows == nil
}
func isEmpty(section: Int) -> Bool {
return self.numberOfRows(inSection: section) == 0
}
var lastSection: Int {
return self.numberOfSections - 1
}
var lastSectionWithRows: Int? {
if self.numberOfSections == 0 { return nil }
var section = self.numberOfSections - 1
if section < 0 { return nil }
while section >= 0 {
if self.numberOfRows(inSection: section) != 0 { return section }
section -= 1
}
return nil
}
var firstSectionWithRows: Int? {
if self.numberOfSections == 0 { return nil }
var section = 0
if section > self.numberOfSections - 1 { return nil }
while section <= (self.numberOfSections - 1) {
if self.numberOfRows(inSection: section) != 0 { return section }
section += 1
}
return nil
}
}
@@ -0,0 +1,45 @@
// 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 UITableViewCell {
public var accessoryView: UIView? {
return subviews.compactMap { $0 as? UIButton }.first
}
var highlightedColor: UIColor? {
get {
return self.backgroundView?.backgroundColor
}
set {
let backgroundView = UIView()
backgroundView.backgroundColor = SPNativeColors.customGray
self.selectedBackgroundView = backgroundView
}
}
public func highlight() {
self.setHighlighted(true, animated: false)
self.setHighlighted(false, animated: true)
}
}
@@ -0,0 +1,39 @@
// 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 UITextField {
var isEmptyText: Bool {
get {
if self.text == "" {
return true
}
if self.text == nil {
return true
}
return false
}
}
}
@@ -0,0 +1,175 @@
// 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 Photos
extension UIViewController {
public func present(_ viewControllerToPresent: UIViewController, completion: (() -> Swift.Void)? = nil) {
self.present(viewControllerToPresent, animated: true, completion: completion)
}
@objc public func dismiss() {
self.dismiss(animated: true, completion: nil)
}
public func wrapToNavigationController(statusBar: SPStatusBar = .dark) -> UINavigationController {
let controller = SPStatusBarManagerNavigationController(rootViewController: self)
controller.statusBar = statusBar
return controller
}
}
extension UIViewController {
public func dismissKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
@objc public func dismissKeyboard() {
view.endEditing(true)
}
}
extension UIViewController {
public func save(image: UIImage) {
if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)
} else {
print("Saving image error. Not allowed permission")
}
}
public func saveVideo(url: String, complection: @escaping (Bool)->()) {
DispatchQueue.global(qos: .utility).async {
let urls = URL(string: url)
let urldata = try? Data(contentsOf: urls!)
if urldata != nil {
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let filepath = "\(documentsPath)/tempfile.mp4"
DispatchQueue.main.async {
let urlsave = URL(fileURLWithPath: filepath)
do {
try urldata!.write(to: urlsave, options: Data.WritingOptions.atomic)
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: urlsave)
}, completionHandler: { (status, error) in
try? FileManager.default.removeItem(atPath: filepath)
DispatchQueue.main.async {
complection(error == nil)
}
})
} catch {
try? FileManager.default.removeItem(atPath: filepath)
complection(false)
}
}
}
}
}
@objc func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
if let _ = error {
self.imageSaved(isSuccses: false)
} else {
self.imageSaved(isSuccses: true)
}
}
@objc func imageSaved(isSuccses: Bool) {
fatalError("SPUIViewControllerExtenshion - Need ovveride 'imageSaved' func")
}
}
extension UIViewController {
func setPrefersLargeNavigationTitle(_ title: String, smallScreenToSmallBar: Bool = true) {
self.navigationItem.title = title
if #available(iOS 11.0, *) {
self.navigationItem.largeTitleDisplayMode = .automatic
self.navigationController?.navigationBar.prefersLargeTitles = true
}
if smallScreenToSmallBar {
if self.view.frame.width < 375 {
self.setNavigationTitle(title, style: .small)
}
}
}
public func setNavigationTitle(_ title: String, style: SPNavigationTitleStyle) {
self.navigationItem.title = title
switch style {
case .large:
if #available(iOS 11.0, *) {
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationItem.largeTitleDisplayMode = .always
}
case .small:
if #available(iOS 11.0, *) {
self.navigationItem.largeTitleDisplayMode = .never
}
case .stork:
if #available(iOS 11.0, *) {
self.navigationItem.largeTitleDisplayMode = .never
}
}
}
}
extension UIViewController {
public var safeArea: UIEdgeInsets {
if #available(iOS 11.0, *) {
return self.view.safeAreaInsets
} else {
return UIEdgeInsets.zero
}
}
public var navigationBarHeight: CGFloat {
return self.navigationController?.navigationBar.frame.height ?? 0
}
public static var statusBarHeight: CGFloat {
return UIApplication.shared.statusBarFrame.height
}
}
extension UIViewController {
public var navigationTitleColor: UIColor? {
get {
return (self.navigationController?.navigationBar.titleTextAttributes?[NSAttributedString.Key.foregroundColor] as? UIColor) ?? nil
}
set {
let textAttributes: [NSAttributedString.Key: Any]? = [NSAttributedString.Key.foregroundColor: newValue ?? UIColor.black]
self.navigationController?.navigationBar.titleTextAttributes = textAttributes
if #available(iOS 11.0, *) {
self.navigationController?.navigationBar.largeTitleTextAttributes = textAttributes
}
}
}
}
@@ -0,0 +1,282 @@
// 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 extension UIView {
var viewController: UIViewController? {
get {
if let nextResponder = self.next as? UIViewController { return nextResponder }
else if let nextResponder = self.next as? UIView { return nextResponder.viewController }
else { return nil }
}
}
}
public extension UIView {
var safeArea: UIEdgeInsets {
if #available(iOS 11.0, *) {
return self.safeAreaInsets
} else{
return UIEdgeInsets.zero
}
}
func set(width: CGFloat, height: CGFloat) {
self.setHeight(height)
self.setWidth(width)
}
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 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) {
var width = bounds.width * widthFactor
if maxWidth != nil { width.setIfMore(when: maxWidth!) }
var height = bounds.height * heightFactor
if maxHeight != nil { height.setIfMore(when: maxHeight!) }
self.frame = CGRect.init(x: 0, y: 0, width: width, height: height)
if withCentering {
self.center.x = bounds.width / 2
self.center.y = bounds.height / 2
}
}
func setEqualsBoundsFromSuperview(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.superview!.frame.height))
}
if customHeight != nil {
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: self.superview!.frame.width, height: customHeight!))
}
}
func resize(width: CGFloat) {
let relativeFactor = self.frame.width / self.frame.height
if relativeFactor.isNaN { return }
self.frame = CGRect.init(
x: self.frame.origin.x,
y: self.frame.origin.y,
width: width,
height: width * relativeFactor
)
}
func resize(height: CGFloat) {
let relativeFactor = self.frame.width / self.frame.height
if relativeFactor.isNaN { return }
self.frame = CGRect.init(
x: self.frame.origin.x,
y: self.frame.origin.y,
width: height / relativeFactor,
height: height
)
}
func setYCenteringFromSuperview() {
self.center.y = (self.superview?.frame.height ?? 0) / 2
}
func setXCenteringFromSuperview() {
self.center.x = (self.superview?.frame.width ?? 0) / 2
}
func setToCenterInSuperview() {
self.center = CGPoint.init(x: ((self.superview?.frame.width) ?? 0) / 2, y: ((self.superview?.frame.height) ?? 0) / 2)
}
}
public extension UIView {
func setParalax(amountFactor: CGFloat) {
let amount = self.frame.minSideSize * amountFactor
self.setParalax(amount: amount)
}
func setParalax(amount: CGFloat) {
self.motionEffects.removeAll()
let horizontal = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
horizontal.minimumRelativeValue = -amount
horizontal.maximumRelativeValue = amount
let vertical = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
vertical.minimumRelativeValue = -amount
vertical.maximumRelativeValue = amount
let group = UIMotionEffectGroup()
group.motionEffects = [horizontal, vertical]
self.addMotionEffect(group)
}
}
public extension UIView {
func addGrade(alpha: CGFloat, color: UIColor = UIColor.black) -> UIView {
let gradeView = UIView.init()
gradeView.alpha = 0
self.addSubview(gradeView)
SPConstraints.setEqualSizeSuperview(for: gradeView)
gradeView.alpha = alpha
gradeView.backgroundColor = color
return gradeView
}
}
extension UIView {
func setShadow(
xTranslationFactor: CGFloat,
yTranslationFactor: CGFloat,
widthRelativeFactor: CGFloat,
heightRelativeFactor: CGFloat,
blurRadiusFactor: CGFloat,
shadowOpacity: CGFloat,
cornerRadiusFactor: CGFloat = 0
) {
let shadowWidth = self.frame.width * widthRelativeFactor
let shadowHeight = self.frame.height * heightRelativeFactor
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 shadowPath = UIBezierPath.init(
roundedRect: CGRect.init(x: xTranslation, y: yTranslation, width: shadowWidth, height: shadowHeight),
cornerRadius: cornerRadius
)
let blurRadius = self.frame.minSideSize * blurRadiusFactor
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize.zero
self.layer.shadowOpacity = Float(shadowOpacity)
self.layer.shadowRadius = blurRadius
self.layer.masksToBounds = false
self.layer.shadowPath = shadowPath.cgPath;
}
func setShadow(
xTranslation: CGFloat,
yTranslation: CGFloat,
widthRelativeFactor: CGFloat,
heightRelativeFactor: CGFloat,
blurRadius: CGFloat,
shadowOpacity: CGFloat,
cornerRadius: CGFloat = 0
) {
let shadowWidth = self.frame.width * widthRelativeFactor
let shadowHeight = self.frame.height * heightRelativeFactor
let shadowPath = UIBezierPath.init(
roundedRect: CGRect.init(x: xTranslation, y: yTranslation, width: shadowWidth, height: shadowHeight),
cornerRadius: cornerRadius
)
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize.zero
self.layer.shadowOpacity = Float(shadowOpacity)
self.layer.shadowRadius = blurRadius
self.layer.masksToBounds = false
self.layer.shadowPath = shadowPath.cgPath
}
func removeShadow() {
self.layer.shadowColor = nil
self.layer.shadowOffset = CGSize.zero
self.layer.shadowOpacity = 0
self.layer.shadowPath = nil
}
func addShadowOpacityAnimation(to: CGFloat, duration: CFTimeInterval) {
let animation = CABasicAnimation(keyPath:"shadowOpacity")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
animation.fromValue = self.layer.cornerRadius
animation.fromValue = self.layer.shadowOpacity
animation.toValue = to
animation.duration = duration
self.layer.add(animation, forKey: "shadowOpacity")
self.layer.shadowOpacity = Float(to)
}
}
extension UIView {
func addCornerRadiusAnimation(to: CGFloat, duration: CFTimeInterval) {
let animation = CABasicAnimation(keyPath:"cornerRadius")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
animation.fromValue = self.layer.cornerRadius
animation.toValue = to
animation.duration = duration
self.layer.add(animation, forKey: "cornerRadius")
self.layer.cornerRadius = to
}
func show(duration: TimeInterval = 0.3) {
self.isHidden = false
SPAnimation.animate(duration, animations: {
self.alpha = 1
})
}
func hide(duration: TimeInterval = 0.3) {
SPAnimation.animate(duration, animations: {
self.alpha = 0
}, withComplection: {
self.isHidden = true
})
}
func removeAllAnimations() {
self.layer.removeAllAnimations()
}
}
extension UIView {
func roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
func round() {
self.layer.cornerRadius = self.frame.minSideSize / 2
}
}
@@ -0,0 +1,36 @@
// 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 UIVisualEffectView {
convenience init(style: UIBlurEffect.Style) {
let effect = UIBlurEffect(style: style)
self.init(effect: effect)
}
convenience init(vibrancy style: UIBlurEffect.Style) {
let effect = UIBlurEffect(style: style)
let vibrancyEffect = UIVibrancyEffect(blurEffect: effect)
self.init(effect: vibrancyEffect)
}
}
@@ -0,0 +1,37 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
extension UserDefaults {
func set(stringArray array: [String], forKey key: String) {
self.set(array, forKey: key)
}
func set(boolArray array: [Bool], forKey key: String) {
self.set(array, forKey: key)
}
func boolArray(forKey defaultName: String) -> [Bool] {
return UserDefaults.standard.array(forKey: defaultName) as? [Bool] ?? []
}
}
@@ -0,0 +1,54 @@
// 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 SPLayout {
static func sizeWith(widthFactor: CGFloat, maxWidth: CGFloat?, heightFactor: CGFloat, maxHeight: CGFloat?, relativeSideFactor: CGFloat?, from size: CGSize) -> CGSize {
var widthArea = size.width * widthFactor
var heightArea = size.height * heightFactor
if let maxWidth = maxWidth {
widthArea.setIfMore(when: maxWidth)
}
if let maxHeight = maxHeight {
heightArea.setIfMore(when: maxHeight)
}
var prepareWidth = widthArea
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() {}
}
@@ -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() {}
}
@@ -0,0 +1,59 @@
// 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 SPLocale: String, CaseIterable {
case ru = "ru"
case en = "en"
static var current: SPLocale {
set {
UserDefaults.standard.set([newValue.languageCode], forKey: "AppleLanguages")
}
get {
let locale = Locale.preferredLanguages[0].components(separatedBy: "-")[0]
switch locale {
case "en":
return .en
case "ru":
return .ru
default:
return .ru
}
}
}
var languageCode: String {
return self.rawValue
}
var describtion: String {
switch self {
case .en:
return "English"
case .ru:
return "Русский"
}
}
}
@@ -0,0 +1,74 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
import MessageUI
struct SPMail {
static var canSendEmail: Bool {
return MFMailComposeViewController.canSendMail()
}
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 {
if let url: URL = URL(string: parametrs!) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
}
}
}
}
static func dialog(to email: String, subject: String? = nil, body: String? = nil, on viewController: UIViewController) {
let mailVC = MFMailComposeViewController()
mailVC.mailComposeDelegate = SPMailSingltone.sharedInstance
mailVC.setToRecipients([email])
if subject != nil {
mailVC.setSubject(subject!)
}
if body != nil {
mailVC.setMessageBody(body!, isHTML: false)
}
viewController.present(mailVC, animated: true, completion: nil)
}
fileprivate final class SPMailSingltone: NSObject, MFMailComposeViewControllerDelegate {
static let sharedInstance = SPMailSingltone()
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
}
private init() {}
}
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,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 UserNotifications
struct SPLocalNotification {
static func add(from timeInterval: TimeInterval, body: String, title: String? = nil, identifier: String? = nil) {
let content = UNMutableNotificationContent()
content.body = body
content.title = title ?? ""
content.badge = NSNumber(value: 1)
content.sound = UNNotificationSound.default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false)
let identifier = identifier ?? "\(timeInterval)\(body)\(Int.random(min: 0, max: 1000))"
let notification = UNNotificationRequest(
identifier: identifier,
content: content,
trigger: trigger
)
let center = UNUserNotificationCenter.current()
center.add(notification) { (error) in
if let error = error {
print("SPLocalNotification - \(error)")
}
}
}
static func add(in date: Date, body: String, title: String? = nil, identifier: String? = nil) {
let content = UNMutableNotificationContent()
content.body = body
content.title = title ?? ""
content.badge = NSNumber(value: 1)
content.sound = UNNotificationSound.default
let triggerDate = Calendar.current.dateComponents([.year,.month,.day,.hour,.minute,.second,], from: date)
let trigger = UNCalendarNotificationTrigger.init(dateMatching: triggerDate, repeats: false)
let identifier = identifier ?? "\(date)\(body)\(Int.random(min: 0, max: 1000))"
let notification = UNNotificationRequest(
identifier: identifier,
content: content,
trigger: trigger
)
let center = UNUserNotificationCenter.current()
center.add(notification) { (error) in
if let error = error {
print("SPLocalNotification - \(error)")
}
}
}
static func remove(identifier: String) {
let center = UNUserNotificationCenter.current()
center.removePendingNotificationRequests(withIdentifiers: [identifier])
}
private init() {}
}
@@ -0,0 +1,104 @@
// 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 extension String {
public static func random(count: Int) -> String {
let strings = [
"В доме кардинала от меня не было тайн; не раз видел я, как он усердно перелистывает старинные книги и жадно роется в пыли фамильных рукописей. Когда я как-то упрекнул его за бесполезные бессонные ночи, после которых он впадал в болезненное уныние, он взглянул на меня с горькой улыбкой и раскрыл передо мною историю города Рима. В этой книге, в двадцатой главе жизнеописания папы Александра Шестого, я прочел следующие строки, навсегда оставшиеся в моей памяти",
"По этому поводу между отцом и сыном завязался спор. Цезарь считал, что достаточно применить одно из тех средств, которые он всегда держал наготове для своих ближайших друзей, а именно: пресловутый ключ, которым то одного, то другого просили отпереть некий шкаф. На ключе был крохотный железный шип – недосмотр слесаря. Каждый, кто трудился над тугим замком, накалывал себе палец и на другой день умирал. Был еще перстень с львиной головой, который Цезарь надевал, когда хотел пожать руку той или иной особе. Лев впивался в кожу этих избранных рук, и через сутки наступала смерть.",
"Стол накрыли в папских виноградниках возле Сан-Пьетро-ин-Винколи, в прелестном уголке, понаслышке знакомом кардиналам. Роспильози, в восторге от своего нового звания и предвкушая пир, явился с самым веселым лицом. Спада, человек осторожный и очень любивший своего племянника, молодого офицера, подававшего блистательные надежды, взял лист бумаги, перо и написал свое завещание. Потом он послал сказать племяннику, чтобы тот ждал его у виноградников; но посланный, по-видимому, не застал того дома.",
"Спада знал, что значит приглашение на обед. С тех пор как христианство – глубоко цивилизующая сила – восторжествовало в Риме, уже не центурион являлся объявить от имени тирана: «Цезарь желает, чтобы ты умер», а любезный легат с улыбкой говорил от имени папы: «Его святейшество желает, чтобы вы с ним отобедали»"
]
return String(strings.random()!.prefix(count))
}
}
public extension Int {
public static func random(_ n: Int) -> Int {
return Int(arc4random_uniform(UInt32(n)))
}
public static func random(min: Int, max: Int) -> Int {
return Int(arc4random_uniform(UInt32(max - min - 1))) + min
}
}
public extension Double {
public static func random() -> Double {
return Double(arc4random()) / 0xFFFFFFFF
}
public static func random(min: Double, max: Double) -> Double {
return Double.random() * (max - min) + min
}
}
public extension Float {
public static func random() -> Float {
return Float(arc4random()) / 0xFFFFFFFF
}
public static func random(min: Float, max: Float) -> Float {
return Float.random() * (max - min) + min
}
}
public extension CGFloat {
public static func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
public static func random(min: CGFloat, max: CGFloat) -> CGFloat {
return CGFloat.random() * (max - min) + min
}
}
public extension Collection {
func shuffle() -> [Iterator.Element] {
var list = Array(self)
list.shuffleInPlace()
return list
}
}
extension Collection where Index == Int {
func random() -> Iterator.Element? {
return isEmpty ? nil : self[Int(arc4random_uniform(UInt32(endIndex)))]
}
}
public extension MutableCollection where Index == Int {
mutating func shuffleInPlace() {
if count < 2 { return }
for i in startIndex ..< endIndex - 1 {
let j = Int(arc4random_uniform(UInt32(endIndex - i))) + i
guard i != j else { continue }
self.swapAt(i, j)
}
}
}
@@ -0,0 +1,24 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
struct SPShadow { private init() {} }
@@ -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
extension SPShadow {
struct DeepStyle {
private init() {}
public 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) {
let xTranslationFactor: CGFloat = 0
let yTranslationFactor: CGFloat = 0.18
var widthRelativeFactor: CGFloat = 0.68
let heightRelativeFactor: CGFloat = 0.78
let blurRadiusFactor: CGFloat = 0.1
let shadowOpacity: CGFloat = 0.3
if view.frame.width < 120 {
widthRelativeFactor = widthRelativeFactor * 0.8
}
var shadowWidth = view.frame.width * widthRelativeFactor
if (view.frame.width - shadowWidth) / 2 > 35 {
shadowWidth = view.frame.width - 70
widthRelativeFactor = shadowWidth / view.frame.width
}
let shadowHeight = view.frame.height * heightRelativeFactor
let xTranslation = (view.frame.width - shadowWidth) / 2 + (view.frame.width * xTranslationFactor)
var yTranslation = (view.frame.height - shadowHeight) / 2 + (view.frame.height * yTranslationFactor)
let minBottomSpace: CGFloat = 6
if (yTranslation + shadowHeight - view.frame.height) < minBottomSpace {
yTranslation = view.frame.height + minBottomSpace - shadowHeight
}
let maxBottomSpace: CGFloat = 12
if (yTranslation + shadowHeight - view.frame.height) > maxBottomSpace {
yTranslation = view.frame.height + maxBottomSpace - shadowHeight
}
var blurRadius = view.frame.minSideSize * blurRadiusFactor
blurRadius.setIfMore(when: 10)
blurRadius.setIfFewer(when: 7)
view.setShadow(
xTranslation: xTranslation,
yTranslation: yTranslation,
widthRelativeFactor: widthRelativeFactor,
heightRelativeFactor: heightRelativeFactor,
blurRadius: blurRadius,
shadowOpacity: shadowOpacity,
cornerRadius: view.layer.cornerRadius
)
}
}
}
extension UIView {
func setDeepShadow() {
SPShadow.DeepStyle.setFor(view: self)
}
}
extension UILabel {
func setDeepShadowForLetters() {
SPShadow.DeepStyle.setFor(label: self)
}
}
@@ -0,0 +1,66 @@
// 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 SPShare {
public struct Native {
static func share(text: String? = nil, fileNames: [String] = [], images: [UIImage] = [], complection: ((_ isSharing: Bool)->())? = nil, sourceView: UIView, on viewController: UIViewController) {
var shareData: [Any] = []
if text != nil {
shareData.append(text!)
}
for file in fileNames {
let path = Bundle.main.path(forResource: file, ofType: "")
if path != nil {
let fileData = URL.init(fileURLWithPath: path!)
shareData.append(fileData)
}
}
for image in images {
shareData.append(image)
}
let shareViewController = UIActivityViewController(activityItems: shareData, applicationActivities: nil)
shareViewController.completionWithItemsHandler = {(activityType: UIActivity.ActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) in
if !completed {
complection?(false)
return
}
complection?(true)
}
shareViewController.modalPresentationStyle = .popover
shareViewController.popoverPresentationController?.sourceView = sourceView
shareViewController.popoverPresentationController?.sourceRect = sourceView.bounds
viewController.present(shareViewController, animated: true, completion: nil)
}
private init() {}
}
private init() {}
}
@@ -0,0 +1,57 @@
// 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 SPInstagram {
static var isSetApp: Bool {
return UIApplication.shared.canOpenURL(URL(string: "instagram://user?username=test")!)
}
static func openPost(id: String) {
let instagramHooks = "instagram://media?id=\(id)"
let instagramUrl = URL(string: instagramHooks)
let safariURL = URL(string: "instagram.com/\(id)")!
if UIApplication.shared.canOpenURL(instagramUrl!) {
UIApplication.shared.open(instagramUrl!, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
} else {
SPApp.open(link: safariURL.absoluteString, redirect: true)
}
}
static func openUser(username: String) {
let instagramHooks = "instagram://user?username=\(username)"
let instagramUrl = URL(string: instagramHooks)
let safariURL = URL(string: "https://instagram.com/\(username)")!
if UIApplication.shared.canOpenURL(instagramUrl!) {
UIApplication.shared.open(instagramUrl!, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
} else {
SPApp.open(link: safariURL.absoluteString, redirect: true)
}
}
private init() {}
}
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,63 @@
// 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 SPTelegram {
static var isSetApp: Bool {
return UIApplication.shared.canOpenURL(URL(string: "tg://msg?text=test")!)
}
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
let urlStringEncoded = text.addingPercentEncoding( withAllowedCharacters: .urlHostAllowed)
let urlOptional = URL(string: "tg://msg?text=\(urlStringEncoded ?? "")")
if let url = urlOptional {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: complection)
} else {
complection(false)
}
} else {
complection(false)
}
}
static func joinChannel(id: String) {
let url = "https://t.me/joinchat/\(id)"
SPApp.open(link: url, redirect: true)
}
static func openBot(username: String) {
var username = username
if username.first == "@" {
username.removeFirst()
}
let url = "https://telegram.me/\(username)"
SPApp.open(link: url, redirect: true)
}
private init() {}
}
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,49 @@
// 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 SPTwitter {
static var isSetApp: Bool {
return UIApplication.shared.canOpenURL(URL(string: "twitter://post?message=test")!)
}
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
let urlStringEncoded = text.addingPercentEncoding( withAllowedCharacters: .urlHostAllowed)
let urlOptional = URL(string: "twitter://post?message=\(urlStringEncoded ?? "")")
if let url = urlOptional {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: complection)
} else {
complection(false)
}
} else {
complection(false)
}
}
private init() {}
}
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,49 @@
// 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 SPViber {
static var isSetApp: Bool {
return UIApplication.shared.canOpenURL(URL(string: "viber://forward?text=test")!)
}
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
let urlStringEncoded = text.addingPercentEncoding( withAllowedCharacters: .urlHostAllowed)
let urlOptional = URL(string: "viber://forward?text=\(urlStringEncoded ?? "")")
if let url = urlOptional {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: complection)
} else {
complection(false)
}
} else {
complection(false)
}
}
private init() {}
}
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,49 @@
// 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 SPWhatsApp {
static var isSetApp: Bool {
return UIApplication.shared.canOpenURL(URL(string: "whatsapp://send?text=test")!)
}
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
let urlStringEncoded = text.addingPercentEncoding( withAllowedCharacters: .urlHostAllowed)
let urlOptional = URL(string: "whatsapp://send?text=\(urlStringEncoded ?? "")")
if let url = urlOptional {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: complection)
} else {
complection(false)
}
} else {
complection(false)
}
}
private init() {}
}
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,62 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
import CoreSpotlight
import MobileCoreServices
struct SPSpotlight {
static let domainIdentifier = "by.ivanvorobei"
static func addItem(identifier: String, title: String, description: String, addedData: Date? = nil, keywords: [String] = []) {
if #available(iOS 9.0, *) {
let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeMessage as String)
attributeSet.title = title
attributeSet.contentDescription = description
attributeSet.keywords = keywords
if addedData != nil {
attributeSet.contentCreationDate = addedData!
}
let item = CSSearchableItem(uniqueIdentifier: "\(identifier)", domainIdentifier: SPSpotlight.domainIdentifier, attributeSet: attributeSet)
CSSearchableIndex.default().indexSearchableItems([item]) { error in
if let error = error {
print("SPSpotlight addItem error: \(error.localizedDescription)")
}
}
}
}
static func removeItem(identifier: String) {
if #available(iOS 9.0, *) {
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ["\(identifier)"]) { error in
if let error = error {
print("SPSpotlight removeItem error: \(error.localizedDescription)")
}
}
}
}
private init() {}
}
@@ -0,0 +1,75 @@
// 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 enum SPStatusBar {
case dark
case light
}
public enum SPSystemIconType {
case share
case close
case favorite
case favorite_fill
}
public enum SPSocialNetwork {
case whatsapp
case telegram
case vk
case facebook
case viber
}
public enum SPOauthState {
case succsess
case unvalidLogin
case invalidLogin
case unvalidPassword
case invalidPassword
case needTwoFactor
case error
}
public enum SPSeparatorInsetStyle {
case beforeImage
case all
case none
case auto
}
public enum SPNavigationTitleStyle {
case large
case small
case stork
}
public enum SPSystemApp {
case photos
case setting
}
public enum SPSelectionType {
case select
case unselect
}
@@ -0,0 +1,117 @@
// 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 SPAppStoreActionButton: SPDownloadingButton {
var style: Style = .base {
didSet {
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.contentEdgeInsets = UIEdgeInsets.init(top: 6, left: 15, bottom: 6, right: 15)
break
case .main:
self.backgroundColor = self.baseColor
self.layer.borderWidth = 0
self.setTitleColor(UIColor.white)
self.titleLabel?.font = UIFont.system(type: .Bold, size: 14)
self.contentEdgeInsets = UIEdgeInsets.init(top: 6, left: 15, bottom: 6, right: 15)
break
case .buyInStore:
self.backgroundColor = self.baseColor
self.layer.borderWidth = 0
self.setTitleColor(UIColor.white)
self.titleLabel?.font = UIFont.system(type: .Bold, size: 14)
self.contentEdgeInsets = UIEdgeInsets.init(top: 8, left: 15, bottom: 8, right: 15)
break
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.contentEdgeInsets = UIEdgeInsets.init(top: 6, left: 15, bottom: 6, right: 15)
break
}
}
}
var baseColor: UIColor = UIColor.init(hex: "0076FF") {
didSet {
let currentStyle = self.style
self.style = currentStyle
}
}
var secondColor: UIColor = UIColor.init(hex: "F0F1F6") {
didSet {
let currentStyle = self.style
self.style = currentStyle
}
}
override func commonInit() {
self.style = .base
self.layer.masksToBounds = true
}
override func setTitle(_ title: String?, for state: UIControl.State) {
switch self.style {
case .base:
super.setTitle(title?.uppercased(), for: state)
case .main:
super.setTitle(title?.uppercased(), for: state)
case .buyInStore:
super.setTitle(title?.uppercasedFirstLetter(), for: state)
case .line:
super.setTitle(title?.uppercased(), for: state)
}
}
override func layoutSubviews() {
super.layoutSubviews()
if self.style == .buyInStore {
self.layer.cornerRadius = 12
} else {
self.round()
}
if self.style != .main {
if let count = self.titleLabel?.text?.count {
if count < 4 {
self.contentEdgeInsets = UIEdgeInsets.init(top: 6, left: 22, bottom: 6, right: 22)
}
}
}
}
enum Style {
case base
case main
case buyInStore
case line
}
}
@@ -0,0 +1,65 @@
// 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 SPAppleMusicButton: SPButton {
var type: SPSelectionType = .unselect {
didSet {
self.updateType(animated: false)
}
}
var selectColor: UIColor = UIColor.init(hex: "FD2D55") {
didSet {
self.updateType(animated: false)
}
}
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(type: .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
}
}
}
@@ -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.set(width: width, height: sectionHeight)
self.layoutSubviews()
}
override func layoutSubviews() {
super.layoutSubviews()
self.topSeparatorView.frame.origin = .zero
self.topSeparatorView.setWidth(self.frame.width)
self.bottomSeparatorView.frame.origin.x = 0
self.bottomSeparatorView.frame.bottomYPosition = self.frame.height
self.bottomSeparatorView.setWidth(self.frame.width)
let buttonWidth = (self.frame.width - self.buttonsSpace) / 2
self.leftButton.sizeToFit()
self.leftButton.setWidth(buttonWidth)
self.leftButton.frame.origin.x = 0
self.leftButton.center.y = self.frame.height / 2
self.rightButton.sizeToFit()
self.rightButton.setWidth(buttonWidth)
self.rightButton.frame.bottomXPosition = self.frame.width
self.rightButton.center.y = self.frame.height / 2
}
}
@@ -0,0 +1,62 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPButton: UIButton {
var gradientView: SPGradientView? {
didSet {
self.gradientView?.isUserInteractionEnabled = false
if self.gradientView?.superview == nil {
if self.gradientView != nil {
self.insertSubview(self.gradientView!, at: 0)
}
}
}
}
var round: Bool = false {
didSet {
self.layoutSubviews()
}
}
init() {
super.init(frame: CGRect.zero)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
internal func commonInit() {}
override public func layoutSubviews() {
super.layoutSubviews()
self.gradientView?.setEqualsBoundsFromSuperview()
if self.round {
self.round()
}
}
}
@@ -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.setWidth(self.customSideSize)
self.setHeight(self.customSideSize)
self.layoutSubviews()
}
override func layoutSubviews() {
super.layoutSubviews()
let space: CGFloat = 2
let sideSize: CGFloat = 4
let insest: CGFloat = (self.frame.width - (sideSize * 3) - (space * 2)) / 2
var currentXPosition: CGFloat = insest
for dotView in self.dotsView {
dotView.setWidth(sideSize)
dotView.setHeight(sideSize)
dotView.setYCenteringFromSuperview()
dotView.frame.origin.x = currentXPosition
dotView.round()
currentXPosition += (sideSize + space)
}
self.round()
}
}
@@ -0,0 +1,62 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPDownloadingButton: SPButton {
let activityIndicatorView = UIActivityIndicatorView.init()
var isFrameRounded: Bool = false
func startLoading() {
self.activityIndicatorView.alpha = 0
self.activityIndicatorView.isHidden = false
self.activityIndicatorView.startAnimating()
self.activityIndicatorView.color = self.titleLabel?.textColor
self.addSubview(self.activityIndicatorView)
self.hideContent(completion: {
SPAnimation.animate(0.2, animations: {
self.activityIndicatorView.alpha = 1
})
})
}
func stopLoading() {
SPAnimation.animate(0.2, animations: {
self.activityIndicatorView.alpha = 0
}, withComplection: {
self.activityIndicatorView.removeFromSuperview()
self.activityIndicatorView.isHidden = true
self.activityIndicatorView.stopAnimating()
self.showContent()
})
}
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()
}
}
}
@@ -0,0 +1,72 @@
// 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 SPNativeLargeButton: SPDownloadingButton {
override var isHighlighted: Bool {
didSet {
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(0)
if isHighlighted {
self.gradientView?.alpha = 0.7
} else {
self.gradientView?.alpha = 1
}
}
}
}
override func commonInit() {
super.commonInit()
self.titleLabel?.font = UIFont.system(type: UIFont.BoldType.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)
}
override func sizeToFit() {
super.sizeToFit()
if let superview = self.superview {
let sideSpace: CGFloat = superview.frame.width * 0.112
var width = superview.frame.width - sideSpace * 2
width.setIfMore(when: 335)
self.setWidth(width)
}
}
override func layoutSubviews() {
super.layoutSubviews()
self.gradientView?.setEqualsBoundsFromSuperview()
self.gradientView?.layer.cornerRadius = self.layer.cornerRadius
self.gradientView?.gradientLayer.cornerRadius = self.layer.cornerRadius
}
}
@@ -0,0 +1,75 @@
// 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 SPPlayCircleButton: UIButton {
var audioState: AudioState = AudioState.play {
didSet {
switch self.audioState {
case .play:
self.iconView.type = .play
break
case .pause:
self.iconView.type = .pause
break
case .stop:
self.iconView.type = .stop
break
}
}
}
var iconColor = SPNativeColors.white {
didSet {
self.iconView.color = self.iconColor
}
}
let iconView = SPAudioIconView.init()
init() {
super.init(frame: CGRect.zero)
self.addSubview(self.iconView)
self.iconView.isUserInteractionEnabled = false
self.setTitle("", for: .normal)
self.backgroundColor = SPNativeColors.blue
self.audioState = .play
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.iconView.setEqualsFrameFromBounds(self, withWidthFactor: 0.45, withHeightFactor: 0.45, withCentering: true)
self.round()
}
enum AudioState {
case play
case pause
case stop
}
}
@@ -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
class SPSocialButton: UIButton {
let iconView = SPSocialIconView.init()
var widthIconFactor: CGFloat = 0.5
var heightIconFactor: CGFloat = 0.5
var type: SPSocialNetwork {
didSet {
self.iconView.type = self.type
}
}
override var isHighlighted: Bool {
didSet {
if isHighlighted {
self.iconView.color = self.iconView.color.withAlphaComponent(0.7)
} else {
self.iconView.color = self.iconView.color.withAlphaComponent(1)
}
}
}
override var isEnabled: Bool {
didSet {
if isEnabled {
self.alpha = 1
} else {
self.alpha = 0.5
}
}
}
init() {
self.type = .facebook
super.init(frame: CGRect.zero)
self.commonInit()
}
init(type: SPSocialNetwork) {
self.type = type
super.init(frame: CGRect.zero)
self.commonInit()
defer {
self.type = type
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func commonInit() {
self.iconView.isUserInteractionEnabled = false
self.addSubview(self.iconView)
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.round()
}
}
@@ -0,0 +1,79 @@
// 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 SPSystemIconButton: UIButton {
let iconView = SPSystemIconView.init()
var widthIconFactor: CGFloat = 1
var heightIconFactor: CGFloat = 1
var type: SPSystemIconType {
didSet {
self.iconView.type = self.type
}
}
var color = SPNativeColors.blue {
didSet {
self.iconView.color = self.color
}
}
override var isHighlighted: Bool {
didSet {
if isHighlighted {
self.iconView.color = self.color.withAlphaComponent(0.7)
} else {
self.iconView.color = self.color.withAlphaComponent(1)
}
}
}
init() {
self.type = .share
super.init(frame: CGRect.zero)
self.commonInit()
}
init(type: SPSystemIconType) {
self.type = type
super.init(frame: CGRect.zero)
self.iconView.type = self.type
self.type = type
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func 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)
}
}
@@ -0,0 +1,64 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPController: SPStatusBarManagerController {
let emptyTitlesView = SPEmptyTitlesView(title: "No Data", subtitle: "No data or information")
override public func viewDidLoad() {
super.viewDidLoad()
self.emptyTitlesView.isHidden = true
self.view.addSubview(self.emptyTitlesView)
self.updateLayout(with: self.view.frame.size)
}
override public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (contex) in
self.updateLayout(with: size)
}, completion: nil)
}
@available(iOS 11.0, *)
override public func viewLayoutMarginsDidChange() {
super.viewLayoutMarginsDidChange()
self.updateLayout(with: self.view.frame.size)
}
func updateLayout(with size: CGSize) {
self.emptyTitlesView.layout(centerY: size.height / 2)
}
func addHideButton(title: String) {
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(
title: title,
style: UIBarButtonItem.Style.done,
target: self,
action: #selector(self.dismiss(sender:))
)
}
@objc func dismiss(sender: Any) {
self.dismiss()
}
}
@@ -0,0 +1,245 @@
// 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 SPNativeTableController: SPTableController {
let labelTableViewCellIdentifier: String = "labelTableViewCellIdentifier"
let textFieldTableViewCellIdentifier: String = "textFieldTableViewCellIdentifier"
let buttonTableViewCellIdentifier: String = "buttonTableViewCellIdentifier"
let textTableViewCellIdentifier: String = "textTableViewCellIdentifier"
let textInputTableViewCellIdentifier: String = "textInputTableViewCellIdentifier"
let promoTableViewCellIdentifier: String = "promoTableViewCellIdentifier"
let featuredTitleTableViewCellIdentifier: String = "featuredTitleTableViewCellIdentifier"
let mailTableViewCellIdentifier: String = "mailTableViewCellIdentifier"
let imageTableViewCellIdentifier: String = "imageTableViewCellIdentifier"
let proposeTableViewCellIdentifier: String = "proposeTableViewCellIdentifier"
let mengTransformTableViewCell = "mengTransformTableViewCell"
var showTopInsets: Bool = true
var showBottomInsets: Bool = true
var autoTopSpace: Bool = true
var autoBottomSpace: Bool = true
private var autoSpaceHeight: CGFloat = 35
override func viewDidLoad() {
super.viewDidLoad()
self.statusBar = .dark
self.tableView = UITableView.init(frame: self.view.bounds, style: UITableView.Style.grouped)
self.setPrefersLargeNavigationTitle("Title")
if #available(iOS 11.0, *) {
self.tableView.contentInsetAdjustmentBehavior = .always
}
self.tableView.backgroundColor = SPNativeColors.customGray
self.tableView.delaysContentTouches = false
self.tableView.allowsSelection = false
self.tableView.rowHeight = UITableView.automaticDimension
self.tableView.sectionFooterHeight = UITableView.automaticDimension
self.tableView.sectionHeaderHeight = UITableView.automaticDimension
self.tableView.estimatedRowHeight = 44
self.tableView.register(SPFormTextFiledTableViewCell.self, forCellReuseIdentifier: self.textFieldTableViewCellIdentifier)
self.tableView.register(SPFormLabelTableViewCell.self, forCellReuseIdentifier: self.labelTableViewCellIdentifier)
self.tableView.register(SPFormButtonTableViewCell.self, forCellReuseIdentifier: self.buttonTableViewCellIdentifier)
self.tableView.register(SPFormTextTableViewCell.self, forCellReuseIdentifier: self.textTableViewCellIdentifier)
self.tableView.register(SPFormTextInputTableViewCell.self, forCellReuseIdentifier: self.textInputTableViewCellIdentifier)
self.tableView.register(SPPromoTableViewCell.self, forCellReuseIdentifier: self.promoTableViewCellIdentifier)
self.tableView.register(SPFormFeaturedTitleTableViewCell.self, forCellReuseIdentifier: self.featuredTitleTableViewCellIdentifier)
self.tableView.register(SPFormMailTableViewCell.self, forCellReuseIdentifier: self.mailTableViewCellIdentifier)
self.tableView.register(SPImageTableViewCell.self, forCellReuseIdentifier: self.imageTableViewCellIdentifier)
self.tableView.register(SPProposeTableViewCell.self, forCellReuseIdentifier: self.proposeTableViewCellIdentifier)
self.tableView.register(SPMengTransformTableViewCell.self, forCellReuseIdentifier: self.mengTransformTableViewCell)
self.activityIndicatorView.stopAnimating()
self.activityIndicatorView.color = SPNativeColors.gray
self.view.addSubview(self.activityIndicatorView)
self.updateLayout(with: self.view.frame.size)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return super.numberOfSections(in: tableView)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return super.tableView(tableView, numberOfRowsInSection: section)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
fatalError("SPNativeTableViewController - need ivveride cellForRowAt")
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return nil
}
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return nil
}
func dequeueTextFiledTableViewCell(indexPath: IndexPath) -> SPFormTextFiledTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.textFieldTableViewCellIdentifier, for: indexPath as IndexPath) as! SPFormTextFiledTableViewCell
}
func dequeueLabelTableViewCell(indexPath: IndexPath) -> SPFormLabelTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.labelTableViewCellIdentifier, for: indexPath as IndexPath) as! SPFormLabelTableViewCell
}
func dequeueButtonTableViewCell(indexPath: IndexPath) -> SPFormButtonTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.buttonTableViewCellIdentifier, for: indexPath as IndexPath) as! SPFormButtonTableViewCell
}
func dequeueTextTableViewCell(indexPath: IndexPath) -> SPFormTextTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.textTableViewCellIdentifier, for: indexPath as IndexPath) as! SPFormTextTableViewCell
}
func dequeueTextInputTableViewCell(indexPath: IndexPath) -> SPFormTextInputTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: self.textInputTableViewCellIdentifier, for: indexPath as IndexPath) as! SPFormTextInputTableViewCell
cell.currentIndexPath = indexPath
return cell
}
func dequeuePromoTableViewCell(indexPath: IndexPath? = nil) -> SPPromoTableViewCell {
if indexPath == nil {
return tableView.dequeueReusableCell(withIdentifier: self.promoTableViewCellIdentifier) as! SPPromoTableViewCell
} else {
return tableView.dequeueReusableCell(withIdentifier: self.promoTableViewCellIdentifier, for: indexPath! as IndexPath) as! SPPromoTableViewCell
}
}
func dequeueFeaturedTitleTableViewCell(indexPath: IndexPath) -> SPFormFeaturedTitleTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.featuredTitleTableViewCellIdentifier, for: indexPath as IndexPath) as! SPFormFeaturedTitleTableViewCell
}
func dequeueMailTableViewCell(indexPath: IndexPath) -> SPFormMailTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: self.mailTableViewCellIdentifier, for: indexPath as IndexPath) as! SPFormMailTableViewCell
cell.currentIndexPath = indexPath
return cell
}
func dequeueImageTableViewCell(indexPath: IndexPath) -> SPImageTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: self.imageTableViewCellIdentifier, for: indexPath as IndexPath) as! SPImageTableViewCell
cell.currentIndexPath = indexPath
return cell
}
func dequeueProposeTableViewCell(indexPath: IndexPath) -> SPProposeTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.proposeTableViewCellIdentifier, for: indexPath as IndexPath) as! SPProposeTableViewCell
}
func dequeueMengTransformTableViewCell(indexPath: IndexPath) -> SPMengTransformTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.mengTransformTableViewCell, for: indexPath as IndexPath) as! SPMengTransformTableViewCell
}
}
//MARK: - manage selection
extension SPNativeTableController {
override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
if let _ = tableView.cellForRow(at: indexPath) as? SPFormFeaturedTitleTableViewCell {
return false
}
return true
}
}
//MARK: - manage spaces
extension SPNativeTableController {
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
return self.showTopInsets ? super.tableView(tableView, viewForHeaderInSection: section) : nil
} else {
return super.tableView(tableView, viewForHeaderInSection: section)
}
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
if section == self.tableView.lastSection {
return self.showBottomInsets ? super.tableView(tableView, viewForFooterInSection: section) : nil
} else {
return super.tableView(tableView, viewForFooterInSection: section)
}
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
let firstSection = self.tableView.firstSectionWithRows
if section == firstSection {
if self.showTopInsets {
if self.autoTopSpace {
if self.tableView(self.tableView, viewForHeaderInSection: firstSection!) != nil {
return UITableView.automaticDimension
}
if self.tableView(self.tableView, titleForHeaderInSection: firstSection!) != nil {
return UITableView.automaticDimension
}
return self.autoSpaceHeight
} else {
return UITableView.automaticDimension
}
} else {
return 0
}
} else {
if self.tableView.numberOfRows(inSection: section) == 0 {
return 0
} else {
return UITableView.automaticDimension
}
}
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
if section == self.tableView.lastSectionWithRows {
if self.showBottomInsets {
if self.autoBottomSpace {
if self.tableView(self.tableView, viewForFooterInSection: self.tableView.lastSectionWithRows!) != nil {
return UITableView.automaticDimension
}
if self.tableView(self.tableView, titleForFooterInSection: self.tableView.lastSectionWithRows!) != nil {
return UITableView.automaticDimension
}
return self.autoSpaceHeight
} else {
return UITableView.automaticDimension
}
} else {
return 0
}
} else {
if self.tableView.numberOfRows(inSection: section) == 0 {
return 0
} else {
return UITableView.automaticDimension
}
}
}
}
@@ -0,0 +1,287 @@
// 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 SPProposeController: SPController {
private let data: Data
internal let areaView = AreaView()
private var isPresent: Bool = false
private var animationDuration: TimeInterval {
return 0.5
}
private var space: CGFloat {
return 6
}
init(title: String, subtitle: String, buttonTitle: String, imageLink: String? = nil, image: UIImage? = nil, complection: @escaping (_ isConfirmed: Bool)->() = {_ in }) {
self.data = Data(
title: title,
subtitle: subtitle,
buttonTitle: buttonTitle,
imageLink: imageLink,
image: image,
complection: complection
)
super.init(nibName: nil, bundle: nil)
self.modalPresentationStyle = .overCurrentContext
self.view.backgroundColor = UIColor.black.withAlphaComponent(0)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.areaView.isHidden = true
self.areaView.titleLabel.text = self.data.title
self.areaView.subtitleLabel.text = self.data.subtitle
self.areaView.button.setTitle(self.data.buttonTitle, for: UIControl.State.normal)
self.areaView.button.addTarget(self, action: #selector(self.open), for: UIControl.Event.touchUpInside)
self.areaView.closeButton.addTarget(self, action: #selector(self.close), for: UIControl.Event.touchUpInside)
self.view.addSubview(self.areaView)
let panGesture = UIPanGestureRecognizer.init(target: self, action: #selector(self.handleGesture(sender:)))
panGesture.maximumNumberOfTouches = 1
self.areaView.addGestureRecognizer(panGesture)
self.areaView.imageView.setParalax(amount: 0.1)
if let image = self.data.image {
self.areaView.imageView.setImage(image: image, animatable: false)
}
self.updateLayout(with: self.view.frame.size)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.isPresent {
self.present()
self.isPresent = true
}
}
private func present() {
SPVibration.impact(system: .warning)
self.areaView.frame.origin.y = self.view.frame.size.height
self.areaView.isHidden = false
SPAnimationSpring.animate(self.animationDuration, animations: {
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (self.bottomSafeArea / 2) - self.space
}, spring: 1,
velocity: 1,
options: .transitionCurlUp)
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
let hide = {
self.view.backgroundColor = UIColor.black.withAlphaComponent(0)
self.areaView.frame.origin.y = self.view.frame.size.height
}
let dismiss = {
super.dismiss(animated: false) {
completion?()
}
}
if flag {
SPAnimationSpring.animate(self.animationDuration, animations: {
hide()
}, spring: 1,
velocity: 1,
options: .transitionCurlDown,
withComplection: {
dismiss()
})
} else {
hide()
dismiss()
}
}
override func updateLayout(with size: CGSize) {
self.areaView.setWidth(size.width - (self.space * 2))
self.areaView.layoutSubviews()
self.areaView.sizeToFit()
self.areaView.frame.origin.x = self.space
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (self.bottomSafeArea / 2) - self.space
}
@objc func handleGesture(sender: UIPanGestureRecognizer) {
let returnAreaViewToPoint = {
SPAnimationSpring.animate(self.animationDuration, animations: {
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (self.bottomSafeArea / 2) - self.space
}, spring: 1,
velocity: 1,
options: .transitionCurlDown,
withComplection: {
})
}
switch sender.state {
case .began:
break
case .cancelled:
returnAreaViewToPoint()
case .changed:
let translation = sender.translation(in: self.view)
self.areaView.center = CGPoint(x: areaView.center.x + 0, y: areaView.center.y + translation.y / 4)
sender.setTranslation(CGPoint.zero, in: self.view)
case .ended:
returnAreaViewToPoint()
default:
break
}
}
@objc func open() {
self.data.complection(true)
self.dismiss(animated: true)
}
@objc func close() {
self.data.complection(false)
self.dismiss(animated: true)
}
class AreaView: UIView {
let titleLabel = UILabel()
let subtitleLabel = UILabel()
let imageView = SPDownloadingImageView()
let button = SPNativeLargeButton()
let closeButton = SPSystemIconButton(type: SPSystemIconType.close)
var imageSideSize: CGFloat = 160
private let space: CGFloat = 36
init() {
super.init(frame: CGRect.zero)
self.backgroundColor = UIColor.white
self.layer.masksToBounds = true
self.layer.cornerRadius = 34
self.titleLabel.font = UIFont.system(type: .Regular, size: 28)
self.titleLabel.textColor = UIColor.init(hex: "939393")
self.titleLabel.numberOfLines = 1
self.titleLabel.adjustsFontSizeToFitWidth = true
self.titleLabel.minimumScaleFactor = 0.5
self.titleLabel.setCenteringAlignment()
self.addSubview(self.titleLabel)
self.subtitleLabel.font = UIFont.system(type: .Regular, size: 16)
self.subtitleLabel.textColor = SPNativeColors.black
self.subtitleLabel.numberOfLines = 0
self.subtitleLabel.setCenteringAlignment()
self.addSubview(self.subtitleLabel)
self.imageView.gradeView.backgroundColor = UIColor.white
self.imageView.contentMode = .scaleAspectFit
self.imageView.layer.masksToBounds = true
self.addSubview(self.imageView)
self.button.titleLabel?.font = UIFont.system(type: UIFont.BoldType.Medium, size: 15)
self.button.setTitleColor(SPNativeColors.black)
self.button.backgroundColor = UIColor.init(hex: "D4D3DB")
self.addSubview(self.button)
self.closeButton.widthIconFactor = 0.4
self.closeButton.heightIconFactor = 0.4
self.closeButton.backgroundColor = UIColor.init(hex: "EFEFF4")
self.closeButton.color = UIColor.init(hex: "979797")
self.addSubview(self.closeButton)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.titleLabel.sizeToFit()
self.titleLabel.frame.origin.y = self.space * 0.8
self.titleLabel.setWidth(self.frame.width - self.space * 3)
self.titleLabel.setXCenteringFromSuperview()
self.subtitleLabel.sizeToFit()
self.subtitleLabel.frame.origin.y = self.titleLabel.frame.bottomYPosition + 8
self.subtitleLabel.setWidth(self.frame.width - self.space * 2)
self.subtitleLabel.setXCenteringFromSuperview()
self.imageView.frame = CGRect.init(
x: 0, y: self.subtitleLabel.frame.bottomYPosition + self.space / 2,
width: self.imageSideSize,
height: self.imageSideSize
)
self.imageView.setXCenteringFromSuperview()
self.button.sizeToFit()
self.button.setWidth(self.frame.width - self.space * 2)
self.button.frame.origin.y = self.imageView.frame.bottomYPosition + self.space / 2
self.button.setXCenteringFromSuperview()
self.closeButton.frame = CGRect.init(x: 0, y: 0, width: 24, height: 24)
self.closeButton.frame.origin.x = self.frame.width - self.closeButton.frame.width - 20
self.closeButton.frame.origin.y = 20
self.closeButton.round()
}
override func sizeToFit() {
super.sizeToFit()
self.setHeight(self.button.frame.bottomYPosition + self.space)
}
}
struct Data {
var title: String
var subtitle: String
var buttonTitle: String
var imageLink: String?
var image: UIImage?
var complection: (_ isConfirmed: Bool)->()
}
var bottomSafeArea: CGFloat {
var bottomSafeArea: CGFloat = 0
if let window = UIApplication.shared.keyWindow {
if #available(iOS 11.0, *) {
bottomSafeArea = window.safeAreaInsets.bottom
}
} else {
bottomSafeArea = 0
}
return bottomSafeArea
}
}
@@ -0,0 +1,137 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPStatusBarManagerController: UIViewController {
var statusBar: SPStatusBar = .dark {
didSet {
UIView.animate(withDuration: 0.3) { () -> Void in
self.setNeedsStatusBarAppearanceUpdate()
}
}
}
override public var preferredStatusBarStyle: UIStatusBarStyle {
switch self.statusBar {
case .dark:
return .default
case .light:
return .lightContent
}
}
override public var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .slide
}
public var isHiddenStatusBar: Bool = false {
didSet {
UIView.animate(withDuration: 0.3) { () -> Void in
self.setNeedsStatusBarAppearanceUpdate()
}
}
}
override public var prefersStatusBarHidden: Bool {
return isHiddenStatusBar
}
}
public class SPStatusBarManagerTableController: UITableViewController {
var statusBar: SPStatusBar = .dark {
didSet {
UIView.animate(withDuration: 0.3) { () -> Void in
self.setNeedsStatusBarAppearanceUpdate()
}
}
}
override public var preferredStatusBarStyle: UIStatusBarStyle {
switch self.statusBar {
case .dark:
return .default
case .light:
return .lightContent
}
}
override public var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .slide
}
public var isHiddenStatusBar: Bool = false {
didSet {
UIView.animate(withDuration: 0.3) { () -> Void in
self.setNeedsStatusBarAppearanceUpdate()
}
}
}
override public var prefersStatusBarHidden: Bool {
return isHiddenStatusBar
}
}
public class SPStatusBarManagerNavigationController: UINavigationController {
var statusBar: SPStatusBar = .dark {
didSet {
UIView.animate(withDuration: 0.3) { () -> Void in
self.setNeedsStatusBarAppearanceUpdate()
}
}
}
override public var preferredStatusBarStyle: UIStatusBarStyle {
switch self.statusBar {
case .dark:
return .default
case .light:
return .lightContent
}
}
override public var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .slide
}
public var isHiddenStatusBar: Bool = false {
didSet {
UIView.animate(withDuration: 0.3) { () -> Void in
self.setNeedsStatusBarAppearanceUpdate()
}
}
}
override public var prefersStatusBarHidden: Bool {
return isHiddenStatusBar
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
UIView.animate(withDuration: 0.3) { () -> Void in
self.setNeedsStatusBarAppearanceUpdate()
}
}
}
@@ -0,0 +1,108 @@
// 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 SPTableController: SPStatusBarManagerTableController {
var activityIndicatorView = UIActivityIndicatorView.init()
override func viewDidLoad() {
super.viewDidLoad()
self.activityIndicatorView.stopAnimating()
self.activityIndicatorView.color = SPNativeColors.gray
self.view.addSubview(self.activityIndicatorView)
self.updateLayout(with: self.view.frame.size)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (contex) in
self.updateLayout(with: size)
}, completion: nil)
}
@available(iOS 11.0, *)
override func viewLayoutMarginsDidChange() {
super.viewLayoutMarginsDidChange()
self.updateLayout(with: self.view.frame.size)
}
func updateLayout(with size: CGSize) {
self.activityIndicatorView.center = CGPoint.init(
x: size.width / 2,
y: (size.height - self.safeArea.top - self.safeArea.bottom) / 2
)
}
//MARK: - Cache
private var cacheImages: [(link: String, image: UIImage)] = []
func toCache(link: String, image: UIImage?) {
if image == nil {
return
}
if self.fromCahce(link: link) == nil {
self.cacheImages.append((link: link, image: image!))
}
}
func fromCahce(link: String) -> UIImage? {
let cachedData = self.cacheImages.first(where: {
$0.link == link
})
return cachedData?.image
}
//MARK: - Reload Table View
func reloadTableView(animated: Bool, complection: @escaping ()->() = {}) {
if animated {
UIView.transition(
with: self.tableView,
duration: 0.3,
options: [.transitionCrossDissolve, UIView.AnimationOptions.beginFromCurrentState],
animations: {
self.tableView.reloadData()
}, completion: {(state) in
complection()
})
} else {
self.tableView.reloadData()
complection()
}
}
//MARK: - Other
@objc func dismiss(sender: Any) {
self.dismiss()
}
func addHideButton(title: String) {
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(
title: title,
style: UIBarButtonItem.Style.done,
target: self,
action: #selector(self.dismiss(sender:))
)
}
}
@@ -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
class SPAudioIconView: UIView {
var type: IconType {
didSet {
self.setNeedsDisplay()
}
}
var color = SPNativeColors.white {
didSet {
self.setNeedsDisplay()
}
}
init() {
self.type = .play
super.init(frame: CGRect.zero)
self.commonInit()
}
init(type: IconType) {
self.type = type
super.init(frame: CGRect.zero)
self.commonInit()
}
private func commonInit() {
self.backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
super.draw(rect)
switch type {
case .play:
SPCodeDraw.AudioIconPack.drawPlay(frame: rect, resizing: .aspectFit, fillColor: self.color)
break
case .pause:
SPCodeDraw.AudioIconPack.drawPause(frame: rect, resizing: .aspectFit, fillColor: self.color)
break
case .stop:
SPCodeDraw.AudioIconPack.drawStop(frame: rect, resizing: .aspectFit, fillColor: self.color)
break
}
}
enum IconType {
case play
case pause
case stop
}
}
@@ -0,0 +1,125 @@
// 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 SPGolubevIconView: UIView {
var type: IconType {
didSet {
self.setNeedsDisplay()
}
}
var whiteColor = SPNativeColors.white {
didSet {
self.setNeedsDisplay()
}
}
var lightColor = UIColor.init(hex: "C4D8FB") {
didSet {
self.setNeedsDisplay()
}
}
var mediumColor = UIColor.init(hex: "7AA9F8") {
didSet {
self.setNeedsDisplay()
}
}
var darkColor = UIColor.init(hex: "007AFF") {
didSet {
self.setNeedsDisplay()
}
}
init() {
self.type = .ball
super.init(frame: CGRect.zero)
self.commonInit()
}
init(type: IconType) {
self.type = type
super.init(frame: CGRect.zero)
self.commonInit()
}
private func commonInit() {
self.backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
super.draw(rect)
switch type {
case .camera:
SPCodeDraw.GolubevIconPack.drawCamera(frame: rect, resizing: .aspectFit, white: self.whiteColor, light: self.lightColor, medium: self.mediumColor, dark: self.darkColor)
break
case .photoLibrary:
SPCodeDraw.GolubevIconPack.drawPhotoLibrary(frame: rect, resizing: .aspectFit, white: self.whiteColor, light: self.lightColor, medium: self.mediumColor, dark: self.darkColor)
break
case .ball:
SPCodeDraw.GolubevIconPack.drawBall(frame: rect, resizing: .aspectFit, white: self.whiteColor, light: self.lightColor, medium: self.mediumColor, dark: self.darkColor)
break
case .micro:
SPCodeDraw.GolubevIconPack.drawMicro(frame: rect, resizing: .aspectFit, white: self.whiteColor, light: self.lightColor, medium: self.mediumColor, dark: self.darkColor)
break
case .book:
SPCodeDraw.GolubevIconPack.drawBook(frame: rect, resizing: .aspectFit, white: self.whiteColor, light: self.lightColor, medium: self.mediumColor, dark: self.darkColor)
break
case .documents:
SPCodeDraw.GolubevIconPack.drawDocuments(frame: rect, resizing: .aspectFit, white: self.whiteColor, light: self.lightColor, medium: self.mediumColor, dark: self.darkColor)
break
case .calendar:
SPCodeDraw.GolubevIconPack.drawCalendar(frame: rect, resizing: .aspectFit, white: self.whiteColor, light: self.lightColor, medium: self.mediumColor, dark: self.darkColor)
break
case .compass:
SPCodeDraw.GolubevIconPack.drawCompass(frame: rect, resizing: .aspectFit, white: self.whiteColor, light: self.lightColor, medium: self.mediumColor, dark: self.darkColor)
break
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 {
case camera
case photoLibrary
case ball
case micro
case calendar
case book
case documents
case compass
case headphones
case windmill
}
}
@@ -0,0 +1,80 @@
// 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 SPSocialIconView: UIView {
var type: SPSocialNetwork {
didSet {
self.setNeedsDisplay()
}
}
var color = SPNativeColors.blue {
didSet {
self.setNeedsDisplay()
}
}
init() {
self.type = .facebook
super.init(frame: CGRect.zero)
self.commonInit()
}
init(type: SPSocialNetwork) {
self.type = type
super.init(frame: CGRect.zero)
self.commonInit()
}
private func commonInit() {
self.backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
super.draw(rect)
switch type {
case .facebook:
SPCodeDraw.SocialIconPack.drawFacebook(frame: rect, resizing: .aspectFit, fillColor: self.color)
break
case .telegram:
SPCodeDraw.SocialIconPack.drawTelegram(frame: rect, resizing: .aspectFit, fillColor: self.color)
break
case .viber:
SPCodeDraw.SocialIconPack.drawViber(frame: rect, resizing: .aspectFit, fillColor: self.color)
break
case .vk:
SPCodeDraw.SocialIconPack.drawVK(frame: rect, resizing: .aspectFit, fillColor: self.color)
break
case .whatsapp:
SPCodeDraw.SocialIconPack.drawWhatsapp(frame: rect, resizing: .aspectFit, fillColor: self.color)
break
}
}
}
@@ -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
class SPSystemIconView: UIView {
var type: SPSystemIconType {
didSet {
self.setNeedsDisplay()
}
}
var color = SPNativeColors.blue {
didSet {
self.setNeedsDisplay()
}
}
init() {
self.type = .share
super.init(frame: CGRect.zero)
self.commonInit()
}
init(type: SPSystemIconType) {
self.type = type
super.init(frame: CGRect.zero)
self.commonInit()
}
private func commonInit() {
self.backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
super.draw(rect)
switch type {
case .share:
SPCodeDraw.SystemIconPack.drawShare(frame: rect, resizing: .aspectFit, color: self.color)
break
case .close:
SPCodeDraw.SystemIconPack.drawClose(frame: rect, resizing: .aspectFit, color: self.color)
break
case .favorite:
SPCodeDraw.SystemIconPack.drawFavorite(frame: rect, resizing: .aspectFit, color: self.color)
break
case .favorite_fill:
SPCodeDraw.SystemIconPack.drawFavoriteFill(frame: rect, resizing: .aspectFit, color: self.color)
break
}
}
}
@@ -0,0 +1,44 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPCollectionViewCell: UICollectionViewCell {
var currentIndexPath: IndexPath?
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
internal func commonInit() {}
public override func prepareForReuse() {
super.prepareForReuse()
self.currentIndexPath = nil
}
}
@@ -0,0 +1,169 @@
// 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 SPMengTransformCollectionViewCell: SPCollectionViewCell {
let backgroundImageView = SPDownloadingImageView()
let titleLabel = UILabel()
let subtitleLabel = UILabel()
let gradientView = SPGradientView.init()
let gradeView = UIView()
let shadowContainerView = UIView()
var withShadow: Bool = true {
didSet {
if self.withShadow {
shadowContainerView.layer.shadowOpacity = 0.25
shadowContainerView.layer.shadowOffset = CGSize.init(width: 0, height: 10)
shadowContainerView.layer.shadowRadius = 20
} else {
shadowContainerView.layer.shadowOpacity = 0
shadowContainerView.layer.shadowOffset = CGSize.init(width: 0, height: 0)
shadowContainerView.layer.shadowRadius = 0
}
}
}
override func commonInit() {
shadowContainerView.backgroundColor = UIColor.white
shadowContainerView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(shadowContainerView)
shadowContainerView.leadingAnchor.constraint(equalTo:
self.leadingAnchor).isActive = true
shadowContainerView.trailingAnchor.constraint(equalTo:
self.trailingAnchor).isActive = true
shadowContainerView.topAnchor.constraint(equalTo:
self.topAnchor, constant: 0).isActive = true
shadowContainerView.bottomAnchor.constraint(equalTo:
self.bottomAnchor, constant: 0).isActive = true
let contentView = UIView()
contentView.backgroundColor = UIColor.clear
contentView.translatesAutoresizingMaskIntoConstraints = false
shadowContainerView.addSubview(contentView)
contentView.leadingAnchor.constraint(equalTo:
shadowContainerView.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo:
shadowContainerView.trailingAnchor).isActive = true
contentView.topAnchor.constraint(equalTo:
shadowContainerView.topAnchor, constant: 0).isActive = true
contentView.bottomAnchor.constraint(equalTo:
shadowContainerView.bottomAnchor, constant: 0).isActive = true
self.backgroundImageView.contentMode = .scaleAspectFill
self.backgroundImageView.translatesAutoresizingMaskIntoConstraints = false
self.backgroundImageView.layer.masksToBounds = false
contentView.addSubview(self.backgroundImageView)
self.backgroundImageView.leadingAnchor.constraint(equalTo:
contentView.leadingAnchor, constant: 0).isActive = true
self.backgroundImageView.trailingAnchor.constraint(equalTo:
contentView.trailingAnchor, constant: 0).isActive = true
self.backgroundImageView.topAnchor.constraint(equalTo:
contentView.topAnchor, constant: 0).isActive = true
self.backgroundImageView.bottomAnchor.constraint(equalTo:
contentView.bottomAnchor, constant: 0).isActive = true
self.gradientView.startColorPosition = .topLeft
self.gradientView.endColorPosition = .bottomRight
self.gradientView.isHidden = true
self.gradientView.translatesAutoresizingMaskIntoConstraints = false
self.gradientView.layer.masksToBounds = false
contentView.addSubview(self.gradientView)
self.gradientView.leadingAnchor.constraint(equalTo:
contentView.leadingAnchor).isActive = true
self.gradientView.trailingAnchor.constraint(equalTo:
contentView.trailingAnchor).isActive = true
self.gradientView.topAnchor.constraint(equalTo:
contentView.topAnchor, constant: 0).isActive = true
self.gradientView.bottomAnchor.constraint(equalTo:
contentView.bottomAnchor, constant: 0).isActive = true
self.gradeView.isHidden = true
self.gradeView.backgroundColor = UIColor.black
self.gradeView.alpha = 0
self.gradeView.translatesAutoresizingMaskIntoConstraints = false
self.gradeView.layer.masksToBounds = false
contentView.addSubview(self.gradeView)
self.gradeView.leadingAnchor.constraint(equalTo:
contentView.leadingAnchor).isActive = true
self.gradeView.trailingAnchor.constraint(equalTo:
contentView.trailingAnchor).isActive = true
self.gradeView.topAnchor.constraint(equalTo:
contentView.topAnchor, constant: 0).isActive = true
self.gradeView.bottomAnchor.constraint(equalTo:
contentView.bottomAnchor, constant: 0).isActive = true
contentView.layer.masksToBounds = true
shadowContainerView.layer.cornerRadius = 14
contentView.layer.cornerRadius = 14
self.titleLabel.text = ""
self.titleLabel.setDeepShadowForLetters()
self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
self.titleLabel.font = UIFont.system(type: .DemiBold, size: 32)
self.titleLabel.textColor = UIColor.white
self.titleLabel.numberOfLines = 0
contentView.addSubview(self.titleLabel)
self.titleLabel.leadingAnchor.constraint(equalTo:
contentView.leadingAnchor, constant: 20).isActive = true
self.titleLabel.trailingAnchor.constraint(equalTo:
contentView.trailingAnchor, constant: -20).isActive = true
self.titleLabel.topAnchor.constraint(equalTo:
contentView.topAnchor, constant: 20).isActive = true
self.subtitleLabel.text = ""
self.subtitleLabel.setDeepShadowForLetters()
self.subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
self.subtitleLabel.font = UIFont.system(type: .Regular, size: 17)
self.subtitleLabel.textColor = UIColor.white
self.subtitleLabel.numberOfLines = 0
contentView.addSubview(self.subtitleLabel)
self.subtitleLabel.leadingAnchor.constraint(equalTo:
contentView.leadingAnchor, constant: 20).isActive = true
self.subtitleLabel.trailingAnchor.constraint(equalTo:
contentView.trailingAnchor, constant: -20).isActive = true
self.subtitleLabel.bottomAnchor.constraint(equalTo:
contentView.bottomAnchor, constant: -20).isActive = true
}
override func prepareForReuse() {
super.prepareForReuse()
self.backgroundImageView.image = nil
self.titleLabel.text = ""
self.subtitleLabel.text = ""
self.gradientView.isHidden = true
self.gradeView.alpha = 0
self.gradeView.isHidden = true
self.titleLabel.isHidden = false
self.subtitleLabel.isHidden = false
self.titleLabel.alpha = 1
self.subtitleLabel.alpha = 1
}
}
@@ -0,0 +1,268 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPCollectionViewLayout: UICollectionViewFlowLayout {
var itemSpacingFactor: CGFloat = 0.11
var minItemSpace: CGFloat = 0
var maxItemSpace: CGFloat = 100
var scalingOffset: CGFloat = 200
var minimumAlphaFactor: CGFloat = 0.5
var minimumScaleFactor: CGFloat = 0.8
var yCenteringTranslation: CGFloat = 0
var cellSideRatio: CGFloat? = nil
var maxWidth: CGFloat = 350
var minWidth: CGFloat?
var widthFactor: CGFloat = 0.9
var maxHeight: CGFloat = 350
var heightFactor: CGFloat = 0.9
var isGradeItems: Bool = false
var isScaleItems: Bool = false
var isPaging: Bool = false
var pageWidth: CGFloat {
get {
return self.itemSize.width + self.minimumLineSpacing
}
}
var pageHeight: CGFloat {
get {
return self.itemSize.height + self.minimumLineSpacing
}
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init() {
super.init()
self.scrollDirection = .horizontal
self.minimumInteritemSpacing = 0
self.minimumLineSpacing = 0
}
public override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
if !self.isPaging {
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
}
switch self.scrollDirection {
case .horizontal:
let rawPageValue = (self.collectionView!.contentOffset.x) / self.pageWidth
let currentPage = (velocity.x > 0.0) ? floor(rawPageValue) : ceil(rawPageValue);
let nextPage = (velocity.x > 0.0) ? ceil(rawPageValue) : floor(rawPageValue);
let pannedLessThanAPage = abs(1 + currentPage - rawPageValue) > 0.5;
let flicked = abs(velocity.x) > 0.3
var proposedContentOffset = proposedContentOffset
if (pannedLessThanAPage && flicked) {
proposedContentOffset.x = nextPage * self.pageWidth
} else {
proposedContentOffset.x = round(rawPageValue) * self.pageWidth
}
return proposedContentOffset;
case .vertical:
let rawPageValue = (self.collectionView!.contentOffset.y) / self.pageHeight
let currentPage = (velocity.y > 0.0) ? floor(rawPageValue) : ceil(rawPageValue);
let nextPage = (velocity.y > 0.0) ? ceil(rawPageValue) : floor(rawPageValue);
let pannedLessThanAPage = abs(1 + currentPage - rawPageValue) > 0.5;
let flicked = abs(velocity.y) > 0.3
var proposedContentOffset = proposedContentOffset
if (pannedLessThanAPage && flicked) {
proposedContentOffset.y = nextPage * self.pageHeight
} else {
proposedContentOffset.y = round(rawPageValue) * self.pageHeight
}
return proposedContentOffset;
}
}
override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let collectionView = self.collectionView,
let superAttributes = super.layoutAttributesForElements(in: rect) else {
return super.layoutAttributesForElements(in: rect)
}
let contentOffset = collectionView.contentOffset
let size = collectionView.bounds.size
guard case let newAttributesArray as [UICollectionViewLayoutAttributes] = NSArray(array: superAttributes, copyItems: true) else {
return nil
}
switch self.scrollDirection {
case .horizontal:
let visibleRect = CGRect.init(x: contentOffset.x, y: contentOffset.y, width: size.width, height: size.height)
let visibleCenterX = visibleRect.midX
newAttributesArray.forEach {
let distanceFromCenter = visibleCenterX - $0.center.x
let absDistanceFromCenter = min(abs(distanceFromCenter), self.scalingOffset)
if self.isScaleItems {
let scale = absDistanceFromCenter * (self.minimumScaleFactor - 1) / self.scalingOffset + 1
$0.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1)
}
if self.isGradeItems {
let alpha = absDistanceFromCenter * (self.minimumAlphaFactor - 1) / self.scalingOffset + 1
$0.alpha = alpha
}
}
case .vertical:
let visibleRect = CGRect.init(x: contentOffset.x, y: contentOffset.y, width: size.width, height: size.height)
let visibleCenterY: CGFloat = visibleRect.midY + self.yCenteringTranslation
for owner in newAttributesArray {
let distanceFromCenter = visibleCenterY - owner.center.y
let absDistanceFromCenter = min(abs(distanceFromCenter), self.scalingOffset)
if self.isScaleItems {
let scale = absDistanceFromCenter * (self.minimumScaleFactor - 1) / self.scalingOffset + 1
owner.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1)
}
if self.isGradeItems {
let alpha = absDistanceFromCenter * (self.minimumAlphaFactor - 1) / self.scalingOffset + 1
owner.alpha = alpha
}
}
}
return newAttributesArray
}
override public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
var isAllowInsertAnimation: Bool = false
var deleteIndexPaths: [IndexPath] = []
var insertIndexPaths: [IndexPath] = []
public override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
super.prepare(forCollectionViewUpdates: updateItems)
for item in updateItems {
if item.updateAction == .delete {
if item.indexPathBeforeUpdate != nil {
self.deleteIndexPaths.append(item.indexPathBeforeUpdate!)
}
}
if item.updateAction == .insert {
if item.indexPathAfterUpdate != nil {
self.insertIndexPaths.append(item.indexPathAfterUpdate!)
}
}
}
}
public override func finalizeCollectionViewUpdates() {
super.finalizeCollectionViewUpdates()
self.insertIndexPaths.removeAll()
self.deleteIndexPaths.removeAll()
}
override public func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes: UICollectionViewLayoutAttributes? = super.initialLayoutAttributesForAppearingItem(at: itemIndexPath)
if isAllowInsertAnimation {
if self.insertIndexPaths.contains(itemIndexPath) {
attributes?.alpha = 0
attributes?.zIndex = 0
attributes?.transform = CGAffineTransform.init(scaleX: self.minimumScaleFactor, y: self.minimumScaleFactor)
}
}
return attributes
}
override public func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes: UICollectionViewLayoutAttributes? = super.finalLayoutAttributesForDisappearingItem(at: itemIndexPath)
if self.deleteIndexPaths.contains(itemIndexPath) {
attributes?.alpha = 0
attributes?.zIndex = 0
attributes?.transform3D = CATransform3DScale(CATransform3DIdentity, self.minimumScaleFactor, self.minimumScaleFactor, 1)
}
return attributes
}
override public func prepare() {
super.prepare()
guard let collectionView = self.collectionView else {
return
}
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: width,
height: height
)
} else {
self.itemSize = SPLayout.sizeWith(
widthFactor: self.widthFactor,
maxWidth: self.maxWidth,
heightFactor: self.heightFactor,
maxHeight: self.maxHeight,
relativeSideFactor: self.cellSideRatio!,
from: collectionView.bounds.size
)
}
if self.minWidth != nil {
self.itemSize.width.setIfFewer(when: self.minWidth!)
}
switch self.scrollDirection {
case .horizontal:
self.minimumLineSpacing = collectionView.frame.width * itemSpacingFactor
case .vertical:
self.minimumLineSpacing = collectionView.frame.height * itemSpacingFactor
}
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