Compare commits
351 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 47fd132451 | |||
| f0c45211b2 | |||
| 2952d5d559 | |||
| a6a5995402 | |||
| 8e476c702f | |||
| 5f7cc4c7b3 | |||
| d8d4e1e339 | |||
| 0f78641fdb | |||
| fd909a3ad6 | |||
| 2bb0c190b3 | |||
| 1ecc16c953 | |||
| 4698fbbe99 | |||
| 03df02d214 | |||
| cbc46d4db4 | |||
| 552f722a88 | |||
| 19caee5ccc | |||
| 2b5d20f26d | |||
| a6913a46a2 | |||
| fab0c7a274 | |||
| d04463f251 | |||
| d37aace4ec | |||
| 33d9b98d7d | |||
| 0363923e1b | |||
| 877108b08b | |||
| 7b25a6b878 | |||
| b69ae3ff7e | |||
| 4eac2276d4 | |||
| 38a6a10280 | |||
| aae897fc68 | |||
| 438811dbe5 | |||
| 7ea761b26e | |||
| 5c5457a09b | |||
| cdccba8929 | |||
| 196beb7592 | |||
| 91fe62e899 | |||
| bab71b560d | |||
| bb11bcd528 | |||
| d1f25b147f | |||
| 3eb459eab5 | |||
| 9060761733 | |||
| d7c5bd193c | |||
| 43a03de8d9 | |||
| 621bdb3e05 | |||
| d50ba88425 | |||
| 08b8422c26 | |||
| 9d13d508c6 | |||
| 6ae45f0568 | |||
| e5dbab65e1 | |||
| 07827b28a9 | |||
| 1dbd0ad54b | |||
| 49f2429633 | |||
| ef0a87e777 | |||
| b23a0b14e3 | |||
| a05b878af4 | |||
| 912c2d3903 | |||
| 3716912bc5 | |||
| ec9e174e14 | |||
| 6b98bf40c2 | |||
| 1167ca0a0d | |||
| c8c565d4ec | |||
| be0be7585b | |||
| c241629ea6 | |||
| 46afa63dbd | |||
| 682ab6127f | |||
| 4d70e9ca26 | |||
| 7091a625f3 | |||
| 5a83c3d1f3 | |||
| ad0c6eaf62 | |||
| 1f96f32980 | |||
| 03b38dfa0e | |||
| 2c35358336 | |||
| 18626df942 | |||
| 1a716980c0 | |||
| b5cf71046f | |||
| 328d3739d6 | |||
| 2a858aa309 | |||
| f2937f6830 | |||
| c274892031 | |||
| 745968a053 | |||
| 4c90c708b7 | |||
| 871e96c1b9 | |||
| ad26638886 | |||
| 0306b46949 | |||
| 0f038463ed | |||
| ae01552b84 | |||
| 272bff5584 | |||
| 2adb5b4c88 | |||
| f930141a04 | |||
| 615fb4fcbc | |||
| 10dafb3853 | |||
| 7505c48033 | |||
| 35770b7362 | |||
| d478decba7 | |||
| 51bb03327c | |||
| f23eacf2ad | |||
| 04c9b77c75 | |||
| e7e83b5ad2 | |||
| 5911771ec9 | |||
| 6c1070848f | |||
| c18e8b8faf | |||
| 1d03424ab4 | |||
| 66556da572 | |||
| 6ebcfa59e9 | |||
| f6a2c31e2f | |||
| be3eb3f3a7 | |||
| a821389fee | |||
| e057aadaf1 | |||
| 4c95e0ed3f | |||
| 71708b43c3 | |||
| 66d7775d01 | |||
| 298e0fdfc0 | |||
| 65070bce00 | |||
| 2210afc7a7 | |||
| 94a8b06dde | |||
| 621e19cf78 | |||
| 6ae5e4f73d | |||
| 57d927bc49 | |||
| 1817c81bcc | |||
| 02e77c6074 | |||
| c922a8f522 | |||
| 86e7b3458d | |||
| a7ffa44434 | |||
| 96cc6b021a | |||
| 398da17b4a | |||
| 5e76f9f4d0 | |||
| a20a025a5b | |||
| 93e5b6f9de | |||
| 9d280b9b51 | |||
| 3d486c8f4b | |||
| d8fc226c9a | |||
| 7fd5eb41a6 | |||
| 263996e39d | |||
| b36cc7720d | |||
| f01ff0e904 | |||
| 92f29009c4 | |||
| b9813933b9 | |||
| ef0169a429 | |||
| 75766e6b7f | |||
| 9e8ed72013 | |||
| 29d6f3af93 | |||
| afa7e48e4c | |||
| b4c022e889 | |||
| 3c6e7e2c9c | |||
| 16e4685963 | |||
| e8e0e22259 | |||
| 19551282e6 | |||
| bc66345c6a | |||
| 73152ed8df | |||
| 431e9f58d5 | |||
| c3d75dac5e | |||
| cf9e7501d2 | |||
| 182ee5f0f0 | |||
| b2b37e717c | |||
| c0ea9d29a1 | |||
| cb55b4861a | |||
| 6a0aa725f7 | |||
| aebe62a9f2 | |||
| a0c0bf6885 | |||
| c0be873778 | |||
| 7fa550f79c | |||
| 6a22bb178b | |||
| 8993c2011d | |||
| 70199b65ee | |||
| 99ed2e84e1 | |||
| 6d8bf7c70a | |||
| a21dd49806 | |||
| 2cb195fcab | |||
| 4c78642040 | |||
| 73d297de19 | |||
| 1d167c9506 | |||
| 0b07e8e2cb | |||
| 8500571554 | |||
| 13134f2748 | |||
| 6e1a5c85b3 | |||
| eab9045cb5 | |||
| f567582b4d | |||
| 30365fde24 | |||
| 6b8fa51706 | |||
| 1f1aa2ea8d | |||
| 914ce0f689 | |||
| 5dfab81504 | |||
| 9270185bf6 | |||
| 43bc6c27ee | |||
| 551ccf3bf1 | |||
| 0488521869 | |||
| 9eae918f9d | |||
| 28ed6b6ebc | |||
| b783329b69 | |||
| 84256152dc | |||
| ac366c84fd | |||
| 64bebe0645 | |||
| 1efa55aefb | |||
| e5371ca20c | |||
| fdbc1e3527 | |||
| 306547671c | |||
| 1fede9eb64 | |||
| 3ef2ff81bc | |||
| 83330bb509 | |||
| c4da6ff27e | |||
| 686bbd749b | |||
| 7b151fd268 | |||
| 7bdf702703 | |||
| 0d1381d941 | |||
| 39c30a030d | |||
| 9e4f6e086f | |||
| 5bc31cdc57 | |||
| 7d53ed31e6 | |||
| 80806de694 | |||
| 8d8458cc34 | |||
| 6e09df910e | |||
| 253f60cfa9 | |||
| 81ed7b01cd | |||
| d0f120d49c | |||
| bc3555e715 | |||
| 432a1206cf | |||
| 488b19fc46 | |||
| edc2b6ea41 | |||
| 79d64599d0 | |||
| 854ab2aef2 | |||
| 04c4929e7d | |||
| 6e4e9713c3 | |||
| e37c43c8f3 | |||
| 001536c835 | |||
| 5a7d01e1e4 | |||
| a1d17d994c | |||
| f56a054b91 | |||
| bd26c4d721 | |||
| 6927d90572 | |||
| ea32992f63 | |||
| 05ba026c1f | |||
| b3f49a2a94 | |||
| c3aa4dc17e | |||
| d9f67b57f8 | |||
| 962d1a937d | |||
| bc6c7ff45e | |||
| ec7abd4a2a | |||
| 6904e0916a | |||
| 90ee3d35da | |||
| 7f998787a4 | |||
| 618da7d9ea | |||
| f062d07f94 | |||
| 926e1f643c | |||
| cdd8f4a2f4 | |||
| 30cf10c595 | |||
| 884791b05c | |||
| 4d0530e0a1 | |||
| 48741988b2 | |||
| 6297d5a5db | |||
| 3f60e48547 | |||
| 2ffe86afb9 | |||
| cf5bf12513 | |||
| 96aa1a6064 | |||
| 3241abd195 | |||
| b72f10c7bd | |||
| 40bcff3cb6 | |||
| 01cc226253 | |||
| 691045d012 | |||
| 59f52f258a | |||
| bfece3194a | |||
| 6cccf24ffa | |||
| 635878e456 | |||
| d839e0f414 | |||
| e3cbc318be | |||
| e4232ec045 | |||
| 02346c0814 | |||
| 0b98593954 | |||
| 518700f9cd | |||
| 5e02985ef2 | |||
| 539589e029 | |||
| 3434c487cd | |||
| c18e72add2 | |||
| c5e407fd80 | |||
| 3937c9caa5 | |||
| 38332bed39 | |||
| 40c7eee5de | |||
| 2e881179ee | |||
| 749640d359 | |||
| fd68fcb9f5 | |||
| 473f8ae0f1 | |||
| 75825b5e0a | |||
| f1e053d0d3 | |||
| 6208c93d40 | |||
| 8c64cd09da | |||
| c36078cda2 | |||
| b78c82194f | |||
| a112473a04 | |||
| c1b135a30a | |||
| c1a7622a26 | |||
| c3421432b0 | |||
| 99353d3610 | |||
| 3bee6898e0 | |||
| 587790b1db | |||
| 2698cfe23d | |||
| 42b82886ca | |||
| 7af77b2596 | |||
| 7c663e7760 | |||
| f561e94110 | |||
| 3bf06ae2b3 | |||
| a6d5e6f97d | |||
| 0f20afb87c | |||
| 2368747150 | |||
| 32a010e1ca | |||
| fce0efaffa | |||
| 1500c272b1 | |||
| ba859e3646 | |||
| 01b8702c4a | |||
| ec809ee1c5 | |||
| a5f41ab3d6 | |||
| 58554f1449 | |||
| 8c1b6e2c8d | |||
| 724635e387 | |||
| dfc0fb8e52 | |||
| 50c9ab4104 | |||
| bb0821d2cc | |||
| ae29eba5b9 | |||
| 0c98c3c699 | |||
| 4ecc8e96d8 | |||
| fac454f41d | |||
| ae68a1be5d | |||
| c111a20c86 | |||
| cc1b1ac604 | |||
| 265f77dd5e | |||
| aacf153d0e | |||
| 5b8fdf6590 | |||
| 883ade3052 | |||
| 44837098ea | |||
| 42063749ed | |||
| 464df2eb5c | |||
| 6f48df5761 | |||
| e331793afc | |||
| 82152b698a | |||
| 75eaaec598 | |||
| d2cc2e424c | |||
| 0ac35b4494 | |||
| 6c2600dea8 | |||
| 26d7422216 | |||
| a4a02b598f | |||
| 0309bb2e0e | |||
| 4e0507f132 | |||
| 8e33ff7614 | |||
| b7ab9e0327 | |||
| 4129f23d00 | |||
| 91b01d83d9 | |||
| 4eda691bd0 | |||
| 0889352299 | |||
| 34f267d43c | |||
| 425f8ec1ca | |||
| 25ea7eab4c | |||
| 16f060820f | |||
| 4c8faf7695 | |||
| 5b843582d4 |
@@ -0,0 +1,8 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: ivanvorobei
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
custom: # https://xcode-shop.com
|
||||
@@ -1 +1,38 @@
|
||||
# Mac OS X
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
## Build generated
|
||||
build/
|
||||
DerivedData
|
||||
|
||||
## Various settings
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
|
||||
## Other
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
*.xcuserstate
|
||||
*.xcscmblueprint
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
*.ipa
|
||||
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
|
||||
# Swift Package Manager
|
||||
.build/
|
||||
|
||||
# Carthage
|
||||
Carthage/Build
|
||||
@@ -7,31 +7,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
|
||||
let navigationController = UINavigationController(rootViewController: Controller())
|
||||
self.launch(rootViewController: navigationController)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
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 launch(rootViewController: UIViewController) {
|
||||
let frame = UIScreen.main.bounds
|
||||
self.window = UIWindow(frame: frame)
|
||||
self.window!.rootViewController = rootViewController
|
||||
self.window!.makeKeyAndVisible()
|
||||
}
|
||||
|
||||
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:.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,93 +1,111 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_20pt@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_20pt@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_29pt@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_29pt@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_40pt@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_40pt@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_60pt@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_60pt@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_20pt.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_20pt@2x-1.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_29pt.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_29pt@2x-1.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_40pt.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_40pt@2x-1.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_76pt.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_76pt@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
|
||||
|
After Width: | Height: | Size: 483 KiB |
|
After Width: | Height: | Size: 685 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,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.bottomY + 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)
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,10 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public struct SPStyleKit {
|
||||
public enum SPFakeBarNavigationStyle {
|
||||
|
||||
private init() {}
|
||||
case large
|
||||
case small
|
||||
case stork
|
||||
case noContent
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
open class SPFakeBarView: UIView {
|
||||
|
||||
public var style: SPFakeBarNavigationStyle = .small {
|
||||
didSet {
|
||||
self.updateStyle()
|
||||
}
|
||||
}
|
||||
|
||||
private var settedHeight: CGFloat = 0
|
||||
public var height: CGFloat {
|
||||
get {
|
||||
return (self.settedHeight) + (self.addStatusBarHeight ? UIApplication.shared.statusBarFrame.height : 0)
|
||||
}
|
||||
set {
|
||||
self.settedHeight = newValue
|
||||
self.updateHeight()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public var addStatusBarHeight: Bool = true {
|
||||
didSet {
|
||||
self.updateHeight()
|
||||
}
|
||||
}
|
||||
|
||||
public var elementsColor: UIColor = SPFakeBarView.navigationElementsColor {
|
||||
didSet {
|
||||
self.leftButton.setTitleColor(self.elementsColor, for: .normal)
|
||||
self.leftButton.setTitleColor(self.elementsColor.withAlphaComponent(0.7), for: .highlighted)
|
||||
self.rightButton.setTitleColor(self.elementsColor, for: .normal)
|
||||
self.rightButton.setTitleColor(self.elementsColor.withAlphaComponent(0.7), for: .highlighted)
|
||||
}
|
||||
}
|
||||
|
||||
public var closeButtonPossition: CloseButtonPosition = .none {
|
||||
didSet {
|
||||
self.leftButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .regular)
|
||||
self.rightButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .regular)
|
||||
switch self.closeButtonPossition {
|
||||
case .left:
|
||||
self.leftButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
|
||||
case .right:
|
||||
self.rightButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var titleLabel = UILabel.init()
|
||||
public var subtitleLabel = UILabel.init()
|
||||
public var leftButton = UIButton.init()
|
||||
public var rightButton = UIButton.init()
|
||||
|
||||
public let separatorView = UIView()
|
||||
public let blurView: UIVisualEffectView = {
|
||||
let effect = UIBlurEffect(style: .extraLight)
|
||||
return UIVisualEffectView.init(effect: effect)
|
||||
}()
|
||||
|
||||
private var titleBottomConstraint: NSLayoutConstraint?
|
||||
private var heightConstraint: NSLayoutConstraint?
|
||||
private var topConstraint: NSLayoutConstraint?
|
||||
private var leadingConstraint: NSLayoutConstraint?
|
||||
private var trailingConstraint: NSLayoutConstraint?
|
||||
|
||||
public init(style: SPFakeBarNavigationStyle) {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.style = style
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.style = .small
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
self.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
self.addSubview(self.blurView)
|
||||
self.blurView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.blurView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
|
||||
self.blurView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
|
||||
self.blurView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
|
||||
self.blurView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
|
||||
|
||||
self.addSubview(self.separatorView)
|
||||
self.separatorView.backgroundColor = UIColor.init(red: 191 / 255.0, green: 191 / 255.0, blue: 191 / 255.0, alpha: 1)
|
||||
self.separatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.separatorView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
|
||||
self.separatorView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
|
||||
self.separatorView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
|
||||
self.separatorView.heightAnchor.constraint(equalToConstant: 0.5).isActive = true
|
||||
|
||||
self.addSubview(self.titleLabel)
|
||||
self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16).isActive = true
|
||||
self.titleLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16).isActive = true
|
||||
self.titleBottomConstraint = self.titleLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -12)
|
||||
self.titleBottomConstraint?.isActive = true
|
||||
|
||||
self.addSubview(self.subtitleLabel)
|
||||
self.subtitleLabel.textColor = UIColor.init(red: 142 / 255.0, green: 142 / 255.0, blue: 146 / 255.0, alpha: 1)
|
||||
self.subtitleLabel.font = UIFont.systemFont(ofSize: 13, weight: .semibold)
|
||||
self.subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.subtitleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16).isActive = true
|
||||
self.subtitleLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16).isActive = true
|
||||
self.subtitleLabel.bottomAnchor.constraint(equalTo: self.titleLabel.topAnchor, constant: 0).isActive = true
|
||||
|
||||
self.leftButton.setTitleColor(self.elementsColor, for: .normal)
|
||||
self.leftButton.setTitleColor(self.elementsColor.withAlphaComponent(0.7), for: .highlighted)
|
||||
self.leftButton.titleLabel?.textAlignment = .left
|
||||
self.leftButton.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
|
||||
self.leftButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 17, bottom: 0, right: 0)
|
||||
self.addSubview(self.leftButton)
|
||||
self.leftButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.leftButton.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
|
||||
self.leftButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -12).isActive = true
|
||||
|
||||
self.rightButton.setTitleColor(self.elementsColor, for: .normal)
|
||||
self.rightButton.setTitleColor(self.elementsColor.withAlphaComponent(0.7), for: .highlighted)
|
||||
self.rightButton.titleLabel?.textAlignment = .right
|
||||
self.rightButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
|
||||
self.rightButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16)
|
||||
self.addSubview(self.rightButton)
|
||||
self.rightButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.rightButton.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
|
||||
self.rightButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -12).isActive = true
|
||||
|
||||
self.closeButtonPossition = .none
|
||||
|
||||
self.setContraints()
|
||||
self.updateStyle()
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.setContraints()
|
||||
}
|
||||
|
||||
private func setContraints() {
|
||||
if let superview = self.superview {
|
||||
if self.topConstraint == nil {
|
||||
|
||||
self.topConstraint = self.topAnchor.constraint(equalTo: superview.topAnchor)
|
||||
self.topConstraint?.isActive = true
|
||||
self.leadingConstraint = self.leadingAnchor.constraint(equalTo: superview.leadingAnchor)
|
||||
self.leadingConstraint?.isActive = true
|
||||
self.trailingConstraint = self.trailingAnchor.constraint(equalTo: superview.trailingAnchor)
|
||||
self.trailingConstraint?.isActive = true
|
||||
|
||||
self.heightConstraint = self.heightAnchor.constraint(equalToConstant: self.height)
|
||||
self.heightConstraint?.isActive = true
|
||||
self.updateHeight()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateStyle() {
|
||||
switch self.style {
|
||||
case .small:
|
||||
if UIApplication.shared.statusBarFrame.height == 44 {
|
||||
self.height = 88 - 44
|
||||
self.titleBottomConstraint?.constant = -12
|
||||
} else {
|
||||
self.height = 64 - 20
|
||||
self.titleBottomConstraint?.constant = -12
|
||||
}
|
||||
self.addStatusBarHeight = true
|
||||
self.titleLabel.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
|
||||
self.titleLabel.textAlignment = .center
|
||||
case .stork:
|
||||
self.height = 66
|
||||
self.titleBottomConstraint?.constant = -12
|
||||
self.addStatusBarHeight = false
|
||||
self.titleLabel.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
|
||||
self.titleLabel.textAlignment = .center
|
||||
case .large:
|
||||
if UIApplication.shared.statusBarFrame.height == 44 {
|
||||
self.height = 140 - 44
|
||||
self.titleBottomConstraint?.constant = -8
|
||||
} else {
|
||||
self.height = 116 - 20
|
||||
self.titleBottomConstraint?.constant = -4
|
||||
}
|
||||
self.addStatusBarHeight = true
|
||||
self.titleLabel.font = UIFont.systemFont(ofSize: 34, weight: .bold)
|
||||
self.titleLabel.textAlignment = .left
|
||||
break
|
||||
case .noContent:
|
||||
self.height = 0
|
||||
self.addStatusBarHeight = true
|
||||
}
|
||||
|
||||
self.updateConstraints()
|
||||
}
|
||||
|
||||
private func updateHeight() {
|
||||
self.heightConstraint?.constant = self.height
|
||||
self.updateConstraints()
|
||||
}
|
||||
|
||||
public enum CloseButtonPosition {
|
||||
case left
|
||||
case right
|
||||
case none
|
||||
}
|
||||
}
|
||||
|
||||
extension SPFakeBarView {
|
||||
|
||||
static var navigationElementsColor: UIColor {
|
||||
get {
|
||||
if UINavigationBar.appearance().tintColor != nil {
|
||||
return UINavigationBar.appearance().tintColor
|
||||
} else {
|
||||
return UIColor.init(red: 0 / 255.0, green: 122 / 255.0, blue: 255 / 255.0, alpha: 1)
|
||||
}
|
||||
}
|
||||
set {
|
||||
UINavigationBar.appearance().tintColor = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import UIKit
|
||||
|
||||
class SPStorkCodeDraw : NSObject {
|
||||
|
||||
private struct Cache {
|
||||
static let gradient: CGGradient = CGGradient(colorsSpace: nil, colors: [UIColor.red.cgColor, UIColor.red.cgColor] as CFArray, locations: [0, 1])!
|
||||
}
|
||||
|
||||
@objc dynamic class var gradient: CGGradient { return Cache.gradient }
|
||||
|
||||
@objc dynamic class func drawClose(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
context.saveGState()
|
||||
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 100, height: 100), target: targetFrame)
|
||||
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
|
||||
context.scaleBy(x: resizedFrame.width / 100, y: resizedFrame.height / 100)
|
||||
|
||||
let bezierPath = UIBezierPath()
|
||||
bezierPath.move(to: CGPoint(x: 92.02, y: 22.92))
|
||||
bezierPath.addLine(to: CGPoint(x: 64.42, y: 50.52))
|
||||
bezierPath.addLine(to: CGPoint(x: 92.02, y: 78.13))
|
||||
bezierPath.addCurve(to: CGPoint(x: 92.02, y: 92.99), controlPoint1: CGPoint(x: 96.13, y: 82.23), controlPoint2: CGPoint(x: 96.13, y: 88.89))
|
||||
bezierPath.addCurve(to: CGPoint(x: 84.59, y: 96.07), controlPoint1: CGPoint(x: 89.97, y: 95.05), controlPoint2: CGPoint(x: 87.28, y: 96.07))
|
||||
bezierPath.addCurve(to: CGPoint(x: 77.16, y: 92.99), controlPoint1: CGPoint(x: 81.9, y: 96.07), controlPoint2: CGPoint(x: 79.22, y: 95.05))
|
||||
bezierPath.addLine(to: CGPoint(x: 49.55, y: 65.38))
|
||||
bezierPath.addLine(to: CGPoint(x: 21.95, y: 92.99))
|
||||
bezierPath.addCurve(to: CGPoint(x: 14.51, y: 96.07), controlPoint1: CGPoint(x: 19.89, y: 95.05), controlPoint2: CGPoint(x: 17.2, y: 96.07))
|
||||
bezierPath.addCurve(to: CGPoint(x: 7.08, y: 92.99), controlPoint1: CGPoint(x: 11.82, y: 96.07), controlPoint2: CGPoint(x: 9.13, y: 95.05))
|
||||
bezierPath.addCurve(to: CGPoint(x: 7.08, y: 78.13), controlPoint1: CGPoint(x: 2.97, y: 88.89), controlPoint2: CGPoint(x: 2.97, y: 82.23))
|
||||
bezierPath.addLine(to: CGPoint(x: 34.69, y: 50.52))
|
||||
bezierPath.addLine(to: CGPoint(x: 7.08, y: 22.92))
|
||||
bezierPath.addCurve(to: CGPoint(x: 7.08, y: 8.04), controlPoint1: CGPoint(x: 2.97, y: 18.8), controlPoint2: CGPoint(x: 2.97, y: 12.15))
|
||||
bezierPath.addCurve(to: CGPoint(x: 21.94, y: 8.04), controlPoint1: CGPoint(x: 11.18, y: 3.94), controlPoint2: CGPoint(x: 17.84, y: 3.94))
|
||||
bezierPath.addLine(to: CGPoint(x: 49.55, y: 35.65))
|
||||
bezierPath.addLine(to: CGPoint(x: 77.16, y: 8.04))
|
||||
bezierPath.addCurve(to: CGPoint(x: 92.02, y: 8.04), controlPoint1: CGPoint(x: 81.26, y: 3.94), controlPoint2: CGPoint(x: 87.92, y: 3.94))
|
||||
bezierPath.addCurve(to: CGPoint(x: 92.02, y: 22.92), controlPoint1: CGPoint(x: 96.13, y: 12.15), controlPoint2: CGPoint(x: 96.13, y: 18.8))
|
||||
bezierPath.close()
|
||||
color.setFill()
|
||||
bezierPath.fill()
|
||||
|
||||
context.restoreGState()
|
||||
|
||||
}
|
||||
|
||||
@objc(StyleKitNameResizingBehavior)
|
||||
enum ResizingBehavior: Int {
|
||||
case aspectFit
|
||||
case aspectFill
|
||||
case stretch
|
||||
case center
|
||||
|
||||
func apply(rect: CGRect, target: CGRect) -> CGRect {
|
||||
if rect == target || target == CGRect.zero {
|
||||
return rect
|
||||
}
|
||||
|
||||
var scales = CGSize.zero
|
||||
scales.width = abs(target.width / rect.width)
|
||||
scales.height = abs(target.height / rect.height)
|
||||
|
||||
switch self {
|
||||
case .aspectFit:
|
||||
scales.width = min(scales.width, scales.height)
|
||||
scales.height = scales.width
|
||||
case .aspectFill:
|
||||
scales.width = max(scales.width, scales.height)
|
||||
scales.height = scales.width
|
||||
case .stretch:
|
||||
break
|
||||
case .center:
|
||||
scales.width = 1
|
||||
scales.height = 1
|
||||
}
|
||||
|
||||
var result = rect.standardized
|
||||
result.size.width *= scales.width
|
||||
result.size.height *= scales.height
|
||||
result.origin.x = target.minX + (target.width - result.width) / 2
|
||||
result.origin.y = target.minY + (target.height - result.height) / 2
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private override init() {}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIViewController {
|
||||
|
||||
public var isPresentedAsStork: Bool {
|
||||
return transitioningDelegate is SPStorkTransitioningDelegate
|
||||
&& modalPresentationStyle == .custom
|
||||
&& presentingViewController != nil
|
||||
}
|
||||
|
||||
public func presentAsStork(_ controller: UIViewController, height: CGFloat? = nil) {
|
||||
let transitionDelegate = SPStorkTransitioningDelegate()
|
||||
transitionDelegate.customHeight = height
|
||||
controller.transitioningDelegate = transitionDelegate
|
||||
controller.modalPresentationStyle = .custom
|
||||
controller.modalPresentationCapturesStatusBarAppearance = true
|
||||
self.present(controller, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
public func presentAsStork(_ controller: UIViewController, height: CGFloat?, showIndicator: Bool, showCloseButton: Bool, complection: (() -> Void)? = nil) {
|
||||
let transitionDelegate = SPStorkTransitioningDelegate()
|
||||
transitionDelegate.customHeight = height
|
||||
transitionDelegate.showCloseButton = showCloseButton
|
||||
transitionDelegate.showIndicator = showIndicator
|
||||
controller.transitioningDelegate = transitionDelegate
|
||||
controller.modalPresentationStyle = .custom
|
||||
controller.modalPresentationCapturesStatusBarAppearance = true
|
||||
self.present(controller, animated: true, completion: complection)
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,9 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
func + (left: CGPoint, right: CGPoint) -> CGPoint {
|
||||
return CGPoint(x: left.x + right.x, y: left.y + right.y)
|
||||
public enum SPStorkHapticMoments {
|
||||
|
||||
case willPresent
|
||||
case willDismiss
|
||||
case willDismissIfRelease
|
||||
}
|
||||
@@ -21,13 +21,9 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UIDevice {
|
||||
@objc public protocol SPStorkControllerDelegate: class {
|
||||
|
||||
public var isIphone: Bool {
|
||||
return UIDevice.current.userInterfaceIdiom == .phone
|
||||
}
|
||||
@objc optional func didDismissStorkBySwipe()
|
||||
|
||||
public var isIpad: Bool {
|
||||
return UIDevice.current.userInterfaceIdiom == .pad
|
||||
}
|
||||
@objc optional func didDismissStorkByTap()
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// 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 SPStorkController {
|
||||
|
||||
static public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if let controller = self.controller(for: scrollView) {
|
||||
if let presentationController = controller.presentationController as? SPStorkPresentationController {
|
||||
let translation = -(scrollView.contentOffset.y + scrollView.contentInset.top)
|
||||
if translation >= 0 {
|
||||
if controller.isBeingPresented { return }
|
||||
scrollView.subviews.forEach {
|
||||
$0.transform = CGAffineTransform(translationX: 0, y: -translation)
|
||||
}
|
||||
presentationController.setIndicator(style: scrollView.isTracking ? .line : .arrow)
|
||||
if translation >= presentationController.translateForDismiss * 0.4 {
|
||||
if !scrollView.isTracking && !scrollView.isDragging {
|
||||
presentationController.presentedViewController.dismiss(animated: true, completion: {
|
||||
presentationController.storkDelegate?.didDismissStorkBySwipe?()
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
if presentationController.pan?.state != UIGestureRecognizer.State.changed {
|
||||
presentationController.scrollViewDidScroll(translation * 2)
|
||||
}
|
||||
} else {
|
||||
presentationController.setIndicator(style: .arrow)
|
||||
presentationController.scrollViewDidScroll(0)
|
||||
}
|
||||
|
||||
if translation < -5 {
|
||||
presentationController.setIndicator(visible: false, forse: (translation < -50))
|
||||
} else {
|
||||
presentationController.setIndicator(visible: true, forse: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static public var topScrollIndicatorInset: CGFloat {
|
||||
return 6
|
||||
}
|
||||
|
||||
static public func updatePresentingController(parent controller: UIViewController) {
|
||||
if let presentationController = controller.presentedViewController?.presentationController as? SPStorkPresentationController {
|
||||
presentationController.updatePresentingController()
|
||||
}
|
||||
}
|
||||
|
||||
static public func updatePresentingController(modal controller: UIViewController) {
|
||||
if let presentationController = controller.presentationController as? SPStorkPresentationController {
|
||||
presentationController.updatePresentingController()
|
||||
}
|
||||
}
|
||||
|
||||
static private func controller(for view: UIView) -> UIViewController? {
|
||||
var nextResponder = view.next
|
||||
while nextResponder != nil && !(nextResponder! is UIViewController) {
|
||||
nextResponder = nextResponder!.next
|
||||
}
|
||||
return nextResponder as? UIViewController
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
public class SPStorkSegue: UIStoryboardSegue {
|
||||
|
||||
public var transitioningDelegate: SPStorkTransitioningDelegate?
|
||||
|
||||
override public func perform() {
|
||||
transitioningDelegate = transitioningDelegate ?? SPStorkTransitioningDelegate()
|
||||
destination.transitioningDelegate = transitioningDelegate
|
||||
destination.modalPresentationStyle = .custom
|
||||
super.perform()
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,11 @@ final class SPStorkDismissingAnimationController: NSObject, UIViewControllerAnim
|
||||
guard let presentedViewController = transitionContext.viewController(forKey: .from) else {
|
||||
return
|
||||
}
|
||||
|
||||
let finalFrameForPresentedView = transitionContext.finalFrame(for: presentedViewController)
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: finalFrameForPresentedView.width, height: finalFrameForPresentedView.height)
|
||||
|
||||
UIView.animate(
|
||||
withDuration: transitionDuration(using: transitionContext),
|
||||
@@ -41,7 +43,7 @@ final class SPStorkDismissingAnimationController: NSObject, UIViewControllerAnim
|
||||
animations: {
|
||||
presentedViewController.view.frame = offscreenFrame
|
||||
}) { finished in
|
||||
transitionContext.completeTransition(finished)
|
||||
transitionContext.completeTransition(finished)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,529 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPStorkPresentationController: UIPresentationController, UIGestureRecognizerDelegate {
|
||||
|
||||
var swipeToDismissEnabled: Bool = true
|
||||
var tapAroundToDismissEnabled: Bool = true
|
||||
var showCloseButton: Bool = false
|
||||
var showIndicator: Bool = true
|
||||
var indicatorColor: UIColor = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1)
|
||||
var hideIndicatorWhenScroll: Bool = false
|
||||
var customHeight: CGFloat? = nil
|
||||
var translateForDismiss: CGFloat = 200
|
||||
var hapticMoments: [SPStorkHapticMoments] = [.willDismissIfRelease]
|
||||
|
||||
var transitioningDelegate: SPStorkTransitioningDelegate?
|
||||
weak var storkDelegate: SPStorkControllerDelegate?
|
||||
|
||||
var pan: UIPanGestureRecognizer?
|
||||
var tap: UITapGestureRecognizer?
|
||||
|
||||
private var closeButton = SPStorkCloseButton()
|
||||
private var indicatorView = SPStorkIndicatorView()
|
||||
private var gradeView: UIView = UIView()
|
||||
private let snapshotViewContainer = UIView()
|
||||
private var snapshotView: UIView?
|
||||
private let backgroundView = UIView()
|
||||
|
||||
private var snapshotViewTopConstraint: NSLayoutConstraint?
|
||||
private var snapshotViewWidthConstraint: NSLayoutConstraint?
|
||||
private var snapshotViewAspectRatioConstraint: NSLayoutConstraint?
|
||||
|
||||
private var workGester: Bool = false
|
||||
private var startDismissing: Bool = false
|
||||
private var afterReleaseDismissing: Bool = false
|
||||
|
||||
private var topSpace: CGFloat {
|
||||
let statusBarHeight: CGFloat = UIApplication.shared.statusBarFrame.height
|
||||
return (statusBarHeight < 25) ? 30 : statusBarHeight
|
||||
}
|
||||
|
||||
private let alpha: CGFloat = 0.51
|
||||
var cornerRadius: CGFloat = 10
|
||||
|
||||
private var scaleForPresentingView: CGFloat {
|
||||
guard let containerView = containerView else { return 0 }
|
||||
let factor = 1 - ((self.cornerRadius + 3) * 2 / containerView.frame.width)
|
||||
return factor
|
||||
}
|
||||
|
||||
private var feedbackGenerator: UIImpactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
|
||||
|
||||
override var presentedView: UIView? {
|
||||
let view = self.presentedViewController.view
|
||||
if view?.frame.origin == CGPoint.zero {
|
||||
view?.frame = self.frameOfPresentedViewInContainerView
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
override var frameOfPresentedViewInContainerView: CGRect {
|
||||
guard let containerView = containerView else { return .zero }
|
||||
let baseY: CGFloat = self.topSpace + 13
|
||||
let maxHeight: CGFloat = containerView.bounds.height - baseY
|
||||
var height: CGFloat = maxHeight
|
||||
|
||||
if let customHeight = self.customHeight {
|
||||
if customHeight < maxHeight {
|
||||
height = customHeight
|
||||
} else {
|
||||
print("SPStorkController - Custom height change to default value. Your height more maximum value")
|
||||
}
|
||||
}
|
||||
return CGRect(x: 0, y: containerView.bounds.height - height, width: containerView.bounds.width, height: height)
|
||||
}
|
||||
|
||||
override func presentationTransitionWillBegin() {
|
||||
super.presentationTransitionWillBegin()
|
||||
|
||||
if !self.hapticMoments.isEmpty {
|
||||
self.feedbackGenerator.prepare()
|
||||
}
|
||||
|
||||
guard let containerView = self.containerView, let presentedView = self.presentedView, let window = containerView.window else { return }
|
||||
|
||||
if self.showIndicator {
|
||||
self.indicatorView.color = self.indicatorColor
|
||||
let tap = UITapGestureRecognizer.init(target: self, action: #selector(self.dismissAction))
|
||||
tap.cancelsTouchesInView = false
|
||||
self.indicatorView.addGestureRecognizer(tap)
|
||||
presentedView.addSubview(self.indicatorView)
|
||||
self.indicatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.indicatorView.widthAnchor.constraint(equalToConstant: 36).isActive = true
|
||||
self.indicatorView.heightAnchor.constraint(equalToConstant: 13).isActive = true
|
||||
self.indicatorView.centerXAnchor.constraint(equalTo: presentedView.centerXAnchor).isActive = true
|
||||
self.indicatorView.topAnchor.constraint(equalTo: presentedView.topAnchor, constant: 12).isActive = true
|
||||
}
|
||||
self.updateLayoutIndicator()
|
||||
self.indicatorView.style = .arrow
|
||||
self.gradeView.alpha = 0
|
||||
|
||||
if self.showCloseButton {
|
||||
self.closeButton.addTarget(self, action: #selector(self.dismissAction), for: .touchUpInside)
|
||||
presentedView.addSubview(self.closeButton)
|
||||
}
|
||||
self.updateLayoutCloseButton()
|
||||
|
||||
let initialFrame: CGRect = presentingViewController.isPresentedAsStork ? presentingViewController.view.frame : containerView.bounds
|
||||
|
||||
containerView.insertSubview(self.snapshotViewContainer, belowSubview: presentedViewController.view)
|
||||
self.snapshotViewContainer.frame = initialFrame
|
||||
self.updateSnapshot()
|
||||
self.snapshotView?.layer.cornerRadius = 0
|
||||
self.backgroundView.backgroundColor = UIColor.black
|
||||
self.backgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||
containerView.insertSubview(self.backgroundView, belowSubview: self.snapshotViewContainer)
|
||||
NSLayoutConstraint.activate([
|
||||
self.backgroundView.topAnchor.constraint(equalTo: window.topAnchor),
|
||||
self.backgroundView.leftAnchor.constraint(equalTo: window.leftAnchor),
|
||||
self.backgroundView.rightAnchor.constraint(equalTo: window.rightAnchor),
|
||||
self.backgroundView.bottomAnchor.constraint(equalTo: window.bottomAnchor)
|
||||
])
|
||||
|
||||
let transformForSnapshotView = CGAffineTransform.identity
|
||||
.translatedBy(x: 0, y: -snapshotViewContainer.frame.origin.y)
|
||||
.translatedBy(x: 0, y: self.topSpace)
|
||||
.translatedBy(x: 0, y: -snapshotViewContainer.frame.height / 2)
|
||||
.scaledBy(x: scaleForPresentingView, y: scaleForPresentingView)
|
||||
.translatedBy(x: 0, y: snapshotViewContainer.frame.height / 2)
|
||||
|
||||
self.addCornerRadiusAnimation(for: self.snapshotView, cornerRadius: self.cornerRadius, duration: 0.6)
|
||||
self.snapshotView?.layer.masksToBounds = true
|
||||
if #available(iOS 11.0, *) {
|
||||
presentedView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
}
|
||||
presentedView.layer.cornerRadius = self.cornerRadius
|
||||
presentedView.layer.masksToBounds = true
|
||||
|
||||
var rootSnapshotView: UIView?
|
||||
var rootSnapshotRoundedView: UIView?
|
||||
|
||||
if presentingViewController.isPresentedAsStork {
|
||||
guard let rootController = presentingViewController.presentingViewController, let snapshotView = rootController.view.snapshotView(afterScreenUpdates: false) else { return }
|
||||
|
||||
containerView.insertSubview(snapshotView, aboveSubview: self.backgroundView)
|
||||
snapshotView.frame = initialFrame
|
||||
snapshotView.transform = transformForSnapshotView
|
||||
snapshotView.alpha = 1 - self.alpha
|
||||
snapshotView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotView.contentMode = .top
|
||||
snapshotView.layer.masksToBounds = true
|
||||
rootSnapshotView = snapshotView
|
||||
|
||||
let snapshotRoundedView = UIView()
|
||||
snapshotRoundedView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotRoundedView.layer.masksToBounds = true
|
||||
containerView.insertSubview(snapshotRoundedView, aboveSubview: snapshotView)
|
||||
snapshotRoundedView.frame = initialFrame
|
||||
snapshotRoundedView.transform = transformForSnapshotView
|
||||
rootSnapshotRoundedView = snapshotRoundedView
|
||||
}
|
||||
|
||||
presentedViewController.transitionCoordinator?.animate(
|
||||
alongsideTransition: { [weak self] context in
|
||||
guard let `self` = self else { return }
|
||||
self.snapshotView?.transform = transformForSnapshotView
|
||||
self.gradeView.alpha = self.alpha
|
||||
}, completion: { _ in
|
||||
self.snapshotView?.transform = .identity
|
||||
rootSnapshotView?.removeFromSuperview()
|
||||
rootSnapshotRoundedView?.removeFromSuperview()
|
||||
})
|
||||
|
||||
if self.hapticMoments.contains(.willPresent) {
|
||||
self.feedbackGenerator.impactOccurred()
|
||||
}
|
||||
}
|
||||
|
||||
override func presentationTransitionDidEnd(_ completed: Bool) {
|
||||
super.presentationTransitionDidEnd(completed)
|
||||
guard let containerView = containerView else { return }
|
||||
self.updateSnapshot()
|
||||
self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView
|
||||
self.snapshotViewContainer.transform = .identity
|
||||
self.snapshotViewContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.snapshotViewContainer.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
|
||||
self.updateSnapshotAspectRatio()
|
||||
|
||||
if self.tapAroundToDismissEnabled {
|
||||
self.tap = UITapGestureRecognizer.init(target: self, action: #selector(self.dismissAction))
|
||||
self.tap?.cancelsTouchesInView = false
|
||||
self.snapshotViewContainer.addGestureRecognizer(self.tap!)
|
||||
}
|
||||
|
||||
if self.swipeToDismissEnabled {
|
||||
self.pan = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan))
|
||||
self.pan!.delegate = self
|
||||
self.pan!.maximumNumberOfTouches = 1
|
||||
self.pan!.cancelsTouchesInView = false
|
||||
self.presentedViewController.view.addGestureRecognizer(self.pan!)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func dismissAction() {
|
||||
self.presentingViewController.view.endEditing(true)
|
||||
self.presentedViewController.view.endEditing(true)
|
||||
self.presentedViewController.dismiss(animated: true, completion: {
|
||||
self.storkDelegate?.didDismissStorkByTap?()
|
||||
})
|
||||
}
|
||||
|
||||
override func dismissalTransitionWillBegin() {
|
||||
super.dismissalTransitionWillBegin()
|
||||
guard let containerView = containerView else { return }
|
||||
self.startDismissing = true
|
||||
|
||||
let initialFrame: CGRect = presentingViewController.isPresentedAsStork ? presentingViewController.view.frame : containerView.bounds
|
||||
|
||||
let initialTransform = CGAffineTransform.identity
|
||||
.translatedBy(x: 0, y: -initialFrame.origin.y)
|
||||
.translatedBy(x: 0, y: self.topSpace)
|
||||
.translatedBy(x: 0, y: -initialFrame.height / 2)
|
||||
.scaledBy(x: scaleForPresentingView, y: scaleForPresentingView)
|
||||
.translatedBy(x: 0, y: initialFrame.height / 2)
|
||||
|
||||
self.snapshotViewTopConstraint?.isActive = false
|
||||
self.snapshotViewWidthConstraint?.isActive = false
|
||||
self.snapshotViewAspectRatioConstraint?.isActive = false
|
||||
self.snapshotViewContainer.translatesAutoresizingMaskIntoConstraints = true
|
||||
self.snapshotViewContainer.frame = initialFrame
|
||||
self.snapshotViewContainer.transform = initialTransform
|
||||
|
||||
let finalCornerRadius = presentingViewController.isPresentedAsStork ? self.cornerRadius : 0
|
||||
let finalTransform: CGAffineTransform = .identity
|
||||
|
||||
self.addCornerRadiusAnimation(for: self.snapshotView, cornerRadius: finalCornerRadius, duration: 0.6)
|
||||
|
||||
var rootSnapshotView: UIView?
|
||||
var rootSnapshotRoundedView: UIView?
|
||||
|
||||
if presentingViewController.isPresentedAsStork {
|
||||
guard let rootController = presentingViewController.presentingViewController, let snapshotView = rootController.view.snapshotView(afterScreenUpdates: false) else { return }
|
||||
|
||||
containerView.insertSubview(snapshotView, aboveSubview: backgroundView)
|
||||
snapshotView.frame = initialFrame
|
||||
snapshotView.transform = initialTransform
|
||||
snapshotView.contentMode = .top
|
||||
rootSnapshotView = snapshotView
|
||||
snapshotView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotView.layer.masksToBounds = true
|
||||
|
||||
let snapshotRoundedView = UIView()
|
||||
snapshotRoundedView.layer.cornerRadius = self.cornerRadius
|
||||
snapshotRoundedView.layer.masksToBounds = true
|
||||
snapshotRoundedView.backgroundColor = UIColor.black.withAlphaComponent(self.alpha)
|
||||
containerView.insertSubview(snapshotRoundedView, aboveSubview: snapshotView)
|
||||
snapshotRoundedView.frame = initialFrame
|
||||
snapshotRoundedView.transform = initialTransform
|
||||
rootSnapshotRoundedView = snapshotRoundedView
|
||||
}
|
||||
|
||||
presentedViewController.transitionCoordinator?.animate(
|
||||
alongsideTransition: { [weak self] context in
|
||||
guard let `self` = self else { return }
|
||||
self.snapshotView?.transform = .identity
|
||||
self.snapshotViewContainer.transform = finalTransform
|
||||
self.gradeView.alpha = 0
|
||||
if self.hapticMoments.contains(.willDismiss) {
|
||||
self.feedbackGenerator.impactOccurred()
|
||||
}
|
||||
}, completion: { _ in
|
||||
rootSnapshotView?.removeFromSuperview()
|
||||
rootSnapshotRoundedView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
override func dismissalTransitionDidEnd(_ completed: Bool) {
|
||||
super.dismissalTransitionDidEnd(completed)
|
||||
guard let containerView = containerView else { return }
|
||||
|
||||
self.backgroundView.removeFromSuperview()
|
||||
self.snapshotView?.removeFromSuperview()
|
||||
self.snapshotViewContainer.removeFromSuperview()
|
||||
self.indicatorView.removeFromSuperview()
|
||||
self.closeButton.removeFromSuperview()
|
||||
|
||||
let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
presentedViewController.view.frame = offscreenFrame
|
||||
presentedViewController.view.transform = .identity
|
||||
}
|
||||
}
|
||||
|
||||
extension SPStorkPresentationController {
|
||||
|
||||
@objc func handlePan(gestureRecognizer: UIPanGestureRecognizer) {
|
||||
guard gestureRecognizer.isEqual(self.pan), self.swipeToDismissEnabled else { return }
|
||||
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
self.workGester = true
|
||||
self.indicatorView.style = .line
|
||||
self.presentingViewController.view.layer.removeAllAnimations()
|
||||
self.presentingViewController.view.endEditing(true)
|
||||
self.presentedViewController.view.endEditing(true)
|
||||
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: containerView)
|
||||
case .changed:
|
||||
self.workGester = true
|
||||
let translation = gestureRecognizer.translation(in: presentedView)
|
||||
if self.swipeToDismissEnabled {
|
||||
self.updatePresentedViewForTranslation(inVerticalDirection: translation.y)
|
||||
} else {
|
||||
gestureRecognizer.setTranslation(.zero, in: presentedView)
|
||||
}
|
||||
case .ended:
|
||||
self.workGester = false
|
||||
let translation = gestureRecognizer.translation(in: presentedView).y
|
||||
if translation >= self.translateForDismiss {
|
||||
self.presentedViewController.dismiss(animated: true, completion: {
|
||||
self.storkDelegate?.didDismissStorkBySwipe?()
|
||||
})
|
||||
} else {
|
||||
self.indicatorView.style = .arrow
|
||||
UIView.animate(
|
||||
withDuration: 0.6,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 1,
|
||||
initialSpringVelocity: 1,
|
||||
options: [.curveEaseOut, .allowUserInteraction],
|
||||
animations: {
|
||||
self.snapshotView?.transform = .identity
|
||||
self.presentedView?.transform = .identity
|
||||
self.gradeView.alpha = self.alpha
|
||||
})
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if let gester = gestureRecognizer as? UIPanGestureRecognizer {
|
||||
let velocity = gester.velocity(in: self.presentedViewController.view)
|
||||
return abs(velocity.y) > abs(velocity.x)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ translation: CGFloat) {
|
||||
if !self.workGester {
|
||||
self.updatePresentedViewForTranslation(inVerticalDirection: translation)
|
||||
}
|
||||
}
|
||||
|
||||
func updatePresentingController() {
|
||||
if self.startDismissing { return }
|
||||
self.updateSnapshot()
|
||||
}
|
||||
|
||||
func setIndicator(style: SPStorkIndicatorView.Style) {
|
||||
self.indicatorView.style = style
|
||||
}
|
||||
|
||||
func setIndicator(visible: Bool, forse: Bool) {
|
||||
guard self.hideIndicatorWhenScroll else { return }
|
||||
let newAlpha: CGFloat = visible ? 1 : 0
|
||||
if forse {
|
||||
self.indicatorView.layer.removeAllAnimations()
|
||||
self.indicatorView.alpha = newAlpha
|
||||
return
|
||||
}
|
||||
if self.indicatorView.alpha == newAlpha {
|
||||
return
|
||||
}
|
||||
UIView.animate(withDuration: 0.18, animations: {
|
||||
self.indicatorView.alpha = newAlpha
|
||||
})
|
||||
}
|
||||
|
||||
private func updatePresentedViewForTranslation(inVerticalDirection translation: CGFloat) {
|
||||
if self.startDismissing { return }
|
||||
|
||||
let elasticThreshold: CGFloat = 120
|
||||
let translationFactor: CGFloat = 1 / 2
|
||||
|
||||
if translation >= 0 {
|
||||
let translationForModal: CGFloat = {
|
||||
if translation >= elasticThreshold {
|
||||
let frictionLength = translation - elasticThreshold
|
||||
let frictionTranslation = 30 * atan(frictionLength / 120) + frictionLength / 10
|
||||
return frictionTranslation + (elasticThreshold * translationFactor)
|
||||
} else {
|
||||
return translation * translationFactor
|
||||
}
|
||||
}()
|
||||
|
||||
self.presentedView?.transform = CGAffineTransform(translationX: 0, y: translationForModal)
|
||||
|
||||
let scaleFactor = 1 + (translationForModal / 5000)
|
||||
self.snapshotView?.transform = CGAffineTransform.init(scaleX: scaleFactor, y: scaleFactor)
|
||||
let gradeFactor = 1 + (translationForModal / 7000)
|
||||
self.gradeView.alpha = self.alpha - ((gradeFactor - 1) * 15)
|
||||
} else {
|
||||
self.presentedView?.transform = CGAffineTransform.identity
|
||||
}
|
||||
|
||||
if self.swipeToDismissEnabled {
|
||||
let afterRealseDismissing = (translation >= self.translateForDismiss)
|
||||
if afterRealseDismissing != self.afterReleaseDismissing {
|
||||
self.afterReleaseDismissing = afterRealseDismissing
|
||||
if self.hapticMoments.contains(.willDismissIfRelease) {
|
||||
self.feedbackGenerator.impactOccurred()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SPStorkPresentationController {
|
||||
|
||||
override func containerViewWillLayoutSubviews() {
|
||||
super.containerViewWillLayoutSubviews()
|
||||
guard let containerView = containerView else { return }
|
||||
self.updateSnapshotAspectRatio()
|
||||
if presentedViewController.view.isDescendant(of: containerView) {
|
||||
self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
coordinator.animate(alongsideTransition: { contex in
|
||||
self.updateLayoutIndicator()
|
||||
self.updateLayoutCloseButton()
|
||||
}, completion: { [weak self] _ in
|
||||
self?.updateSnapshotAspectRatio()
|
||||
self?.updateSnapshot()
|
||||
})
|
||||
}
|
||||
|
||||
private func updateLayoutIndicator() {
|
||||
self.indicatorView.style = .line
|
||||
self.indicatorView.sizeToFit()
|
||||
//self.indicatorView.frame.origin.y = 12
|
||||
//self.indicatorView.center.x = presentedView.frame.width / 2
|
||||
}
|
||||
|
||||
private func updateLayoutCloseButton() {
|
||||
guard let presentedView = self.presentedView else { return }
|
||||
self.closeButton.sizeToFit()
|
||||
self.closeButton.layout(bottomX: presentedView.frame.width - 19, y: 19)
|
||||
}
|
||||
|
||||
private func updateSnapshot() {
|
||||
guard let currentSnapshotView = presentingViewController.view.snapshotView(afterScreenUpdates: true) else { return }
|
||||
self.snapshotView?.removeFromSuperview()
|
||||
self.snapshotViewContainer.addSubview(currentSnapshotView)
|
||||
self.constraints(view: currentSnapshotView, to: self.snapshotViewContainer)
|
||||
self.snapshotView = currentSnapshotView
|
||||
self.snapshotView?.layer.cornerRadius = self.cornerRadius
|
||||
self.snapshotView?.layer.masksToBounds = true
|
||||
if #available(iOS 11.0, *) {
|
||||
snapshotView?.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
}
|
||||
self.gradeView.removeFromSuperview()
|
||||
self.gradeView.backgroundColor = UIColor.black
|
||||
self.snapshotView!.addSubview(self.gradeView)
|
||||
self.constraints(view: self.gradeView, to: self.snapshotView!)
|
||||
}
|
||||
|
||||
private func updateSnapshotAspectRatio() {
|
||||
guard let containerView = containerView, snapshotViewContainer.translatesAutoresizingMaskIntoConstraints == false else { return }
|
||||
|
||||
self.snapshotViewTopConstraint?.isActive = false
|
||||
self.snapshotViewWidthConstraint?.isActive = false
|
||||
self.snapshotViewAspectRatioConstraint?.isActive = false
|
||||
|
||||
let snapshotReferenceSize = presentingViewController.view.frame.size
|
||||
let aspectRatio = snapshotReferenceSize.width / snapshotReferenceSize.height
|
||||
|
||||
self.snapshotViewTopConstraint = snapshotViewContainer.topAnchor.constraint(equalTo: containerView.topAnchor, constant: self.topSpace)
|
||||
self.snapshotViewWidthConstraint = snapshotViewContainer.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: scaleForPresentingView)
|
||||
self.snapshotViewAspectRatioConstraint = snapshotViewContainer.widthAnchor.constraint(equalTo: snapshotViewContainer.heightAnchor, multiplier: aspectRatio)
|
||||
|
||||
self.snapshotViewTopConstraint?.isActive = true
|
||||
self.snapshotViewWidthConstraint?.isActive = true
|
||||
self.snapshotViewAspectRatioConstraint?.isActive = true
|
||||
}
|
||||
|
||||
private func constraints(view: UIView, to superView: UIView) {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
view.topAnchor.constraint(equalTo: superView.topAnchor),
|
||||
view.leftAnchor.constraint(equalTo: superView.leftAnchor),
|
||||
view.rightAnchor.constraint(equalTo: superView.rightAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: superView.bottomAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
private func addCornerRadiusAnimation(for view: UIView?, cornerRadius: CGFloat, duration: CFTimeInterval) {
|
||||
guard let view = view else { return }
|
||||
let animation = CABasicAnimation(keyPath:"cornerRadius")
|
||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
|
||||
animation.fromValue = view.layer.cornerRadius
|
||||
animation.toValue = cornerRadius
|
||||
animation.duration = duration
|
||||
view.layer.add(animation, forKey: "cornerRadius")
|
||||
view.layer.cornerRadius = cornerRadius
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,15 @@ import UIKit
|
||||
final class SPStorkPresentingAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
guard let presentedViewController = transitionContext.viewController(forKey: .to) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let presentedViewController = transitionContext.viewController(forKey: .to) else { return }
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
containerView.addSubview(presentedViewController.view)
|
||||
presentedViewController.view.frame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
|
||||
|
||||
let finalFrameForPresentedView = transitionContext.finalFrame(for: presentedViewController)
|
||||
presentedViewController.view.frame = finalFrameForPresentedView
|
||||
presentedViewController.view.frame.origin.y = containerView.bounds.height
|
||||
|
||||
UIView.animate(
|
||||
withDuration: transitionDuration(using: transitionContext),
|
||||
@@ -23,9 +23,32 @@ import UIKit
|
||||
|
||||
public final class SPStorkTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
|
||||
|
||||
public var swipeToDismissEnabled: Bool = true
|
||||
public var tapAroundToDismissEnabled: Bool = true
|
||||
public var showCloseButton: Bool = false
|
||||
public var showIndicator: Bool = true
|
||||
public var indicatorColor: UIColor = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1)
|
||||
public var hideIndicatorWhenScroll: Bool = false
|
||||
public var customHeight: CGFloat? = nil
|
||||
public var translateForDismiss: CGFloat = 200
|
||||
public var cornerRadius: CGFloat = 10
|
||||
public var hapticMoments: [SPStorkHapticMoments] = [.willDismissIfRelease]
|
||||
public weak var storkDelegate: SPStorkControllerDelegate? = nil
|
||||
|
||||
public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
|
||||
let controller = SPStorkPresentationController(presentedViewController: presented, presenting: presenting)
|
||||
controller.swipeToDismissEnabled = self.swipeToDismissEnabled
|
||||
controller.tapAroundToDismissEnabled = self.tapAroundToDismissEnabled
|
||||
controller.showCloseButton = self.showCloseButton
|
||||
controller.showIndicator = self.showIndicator
|
||||
controller.indicatorColor = self.indicatorColor
|
||||
controller.hideIndicatorWhenScroll = self.hideIndicatorWhenScroll
|
||||
controller.customHeight = self.customHeight
|
||||
controller.translateForDismiss = self.translateForDismiss
|
||||
controller.cornerRadius = self.cornerRadius
|
||||
controller.hapticMoments = self.hapticMoments
|
||||
controller.transitioningDelegate = self
|
||||
controller.storkDelegate = self.storkDelegate
|
||||
return controller
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
open class SPStorkCloseButton: UIButton {
|
||||
|
||||
let iconView = SPStorkCloseView()
|
||||
|
||||
var widthIconFactor: CGFloat = 1
|
||||
var heightIconFactor: CGFloat = 1
|
||||
|
||||
var color = UIColor.blue {
|
||||
didSet {
|
||||
self.iconView.color = self.color
|
||||
}
|
||||
}
|
||||
|
||||
override open var isHighlighted: Bool {
|
||||
didSet {
|
||||
self.iconView.color = self.color.withAlphaComponent(self.isHighlighted ? 0.7 : 1)
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
self.iconView.isUserInteractionEnabled = false
|
||||
self.addSubview(self.iconView)
|
||||
self.backgroundColor = UIColor.init(red: 239 / 255.0, green: 239 / 255.0, blue: 244 / 255.0, alpha: 1)
|
||||
self.color = UIColor.init(red: 142 / 255.0, green: 142 / 255.0, blue: 147 / 255.0, alpha: 1)
|
||||
self.widthIconFactor = 0.36
|
||||
self.heightIconFactor = 0.36
|
||||
}
|
||||
|
||||
func layout(bottomX: CGFloat, y: CGFloat) {
|
||||
self.sizeToFit()
|
||||
self.frame.origin.x = bottomX - self.frame.width
|
||||
self.frame.origin.y = y
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.layer.cornerRadius = self.frame.width / 2
|
||||
self.iconView.frame = CGRect.init(x: 0, y: 0, width: self.frame.width * self.widthIconFactor, height: self.frame.height * self.heightIconFactor)
|
||||
self.iconView.center = CGPoint.init(x: self.frame.width / 2, y: self.frame.height / 2)
|
||||
}
|
||||
|
||||
override open func sizeToFit() {
|
||||
super.sizeToFit()
|
||||
self.frame = CGRect.init(x: self.frame.origin.x, y: self.frame.origin.y, width: 30, height: 30)
|
||||
}
|
||||
}
|
||||
@@ -21,33 +21,30 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class SPCollectionViewCell: UICollectionViewCell {
|
||||
open class SPStorkCloseView: UIView {
|
||||
|
||||
var currentIndexPath: IndexPath?
|
||||
|
||||
public override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
self.currentIndexPath = nil
|
||||
var color = UIColor.blue {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SPCollectionContainerCell<ContentView: UIView>: UICollectionViewCell {
|
||||
|
||||
let view = ContentView.init()
|
||||
var currentIndexPath: IndexPath?
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
private func commonInit() {
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.addSubview(view)
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.view.setEqualsFrameFromBounds(self)
|
||||
override open func draw(_ rect: CGRect) {
|
||||
super.draw(rect)
|
||||
SPStorkCodeDraw.drawClose(frame: rect, resizing: .aspectFit, color: self.color)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,58 +21,69 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPStorkIndicatorView: UIView {
|
||||
open class SPStorkIndicatorView: UIView {
|
||||
|
||||
var style: Style = .line {
|
||||
didSet {
|
||||
switch self.style {
|
||||
case .line:
|
||||
SPAnimationSpring.animate(0.5, animations: {
|
||||
self.animate {
|
||||
self.leftView.transform = .identity
|
||||
self.rightView.transform = .identity
|
||||
}, options: .curveEaseOut)
|
||||
}
|
||||
case .arrow:
|
||||
SPAnimationSpring.animate(0.5, animations: {
|
||||
self.animate {
|
||||
let angle = CGFloat(20 * Float.pi / 180)
|
||||
self.leftView.transform = CGAffineTransform.init(rotationAngle: angle)
|
||||
self.rightView.transform = CGAffineTransform.init(rotationAngle: -angle)
|
||||
}, options: .curveEaseOut)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var color: UIColor = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1) {
|
||||
didSet {
|
||||
self.leftView.backgroundColor = self.color
|
||||
self.rightView.backgroundColor = self.color
|
||||
}
|
||||
}
|
||||
|
||||
private var leftView: UIView = UIView()
|
||||
private var rightView: UIView = UIView()
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.addSubview(self.leftView)
|
||||
self.addSubview(self.rightView)
|
||||
self.leftView.backgroundColor = UIColor.init(hex: "CAC9CF")
|
||||
self.rightView.backgroundColor = UIColor.init(hex: "CAC9CF")
|
||||
self.color = UIColor.init(red: 202/255, green: 201/255, blue: 207/255, alpha: 1)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
override func sizeToFit() {
|
||||
override open func sizeToFit() {
|
||||
super.sizeToFit()
|
||||
self.setWidth(36)
|
||||
self.setHeight(13)
|
||||
self.frame = CGRect.init(x: self.frame.origin.x, y: self.frame.origin.y, width: 36, height: 13)
|
||||
|
||||
let height: CGFloat = 5
|
||||
let correction = height / 2
|
||||
|
||||
self.leftView.frame = CGRect.init(x: 0, y: 0, width: self.frame.width / 2 + correction, height: height)
|
||||
self.leftView.center.y = self.frame.height / 2
|
||||
self.leftView.round()
|
||||
self.leftView.layer.cornerRadius = min(self.leftView.frame.width, self.leftView.frame.height) / 2
|
||||
|
||||
self.rightView.frame = CGRect.init(x: self.frame.width / 2 - correction, y: 0, width: self.frame.width / 2 + correction, height: height)
|
||||
self.rightView.center.y = self.frame.height / 2
|
||||
self.rightView.round()
|
||||
self.rightView.layer.cornerRadius = min(self.leftView.frame.width, self.leftView.frame.height) / 2
|
||||
}
|
||||
|
||||
private func animate(animations: @escaping (() -> Void)) {
|
||||
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.beginFromCurrentState, .curveEaseOut], animations: {
|
||||
animations()
|
||||
})
|
||||
}
|
||||
|
||||
enum Style {
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class SPAnimation {
|
||||
enum SPAnimation {
|
||||
|
||||
static func animate(_ duration: TimeInterval,
|
||||
animations: (() -> Void)!,
|
||||
@@ -47,7 +47,7 @@ public class SPAnimation {
|
||||
withComplection completion: (() -> Void)! = {}) {
|
||||
|
||||
var optionsWithRepeatition = options
|
||||
optionsWithRepeatition.insert([.autoreverse, .repeat])
|
||||
optionsWithRepeatition.insert([.autoreverse, .repeat, .allowUserInteraction])
|
||||
|
||||
self.animate(
|
||||
duration,
|
||||
@@ -21,11 +21,11 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class SPAnimationAlpha {
|
||||
class SPAnimationAlpha {
|
||||
|
||||
fileprivate static let durationListAnimation: TimeInterval = 0.45
|
||||
fileprivate static let coefLenthForTransition: CGFloat = 2.8
|
||||
fileprivate static let delayPerItem: TimeInterval = 0.09
|
||||
static let durationListAnimation: TimeInterval = 0.45
|
||||
static let coefLenthForTransition: CGFloat = 2.8
|
||||
static let delayPerItem: TimeInterval = 0.09
|
||||
|
||||
static func hideList(_ duration: TimeInterval = durationListAnimation,
|
||||
views: [UIView],
|
||||
@@ -21,10 +21,10 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class SPAnimationSpring {
|
||||
class SPAnimationSpring {
|
||||
|
||||
fileprivate static let spring: CGFloat = 1
|
||||
fileprivate static let velocity: CGFloat = 1
|
||||
static let spring: CGFloat = 1
|
||||
static let velocity: CGFloat = 1
|
||||
|
||||
static func animate(_ duration: TimeInterval,
|
||||
animations: (() -> Void)!,
|
||||
@@ -21,11 +21,11 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class SPAnimationUpward {
|
||||
class SPAnimationUpward {
|
||||
|
||||
fileprivate static let durationListAnimation: TimeInterval = 0.45
|
||||
fileprivate static let coefLenthForTransition: CGFloat = 2.8
|
||||
fileprivate static let delayPerItem: TimeInterval = 0.09
|
||||
static let durationListAnimation: TimeInterval = 0.45
|
||||
static let coefLenthForTransition: CGFloat = 2.8
|
||||
static let delayPerItem: TimeInterval = 0.09
|
||||
|
||||
static func hide(_ duration: TimeInterval,
|
||||
view: UIView,
|
||||
@@ -0,0 +1,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
|
||||
|
||||
enum SPApp {
|
||||
|
||||
static var udid: String? {
|
||||
return UIDevice.current.identifierForVendor?.uuidString
|
||||
}
|
||||
|
||||
static var displayName: String? {
|
||||
return Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
|
||||
}
|
||||
|
||||
static var rootController: UIViewController? {
|
||||
return UIApplication.shared.keyWindow?.rootViewController
|
||||
}
|
||||
|
||||
static var safeArea: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
return UIApplication.shared.keyWindow?.safeArea ?? UIEdgeInsets.zero
|
||||
} else {
|
||||
return UIEdgeInsets.zero
|
||||
}
|
||||
}
|
||||
|
||||
static func set(rootController: UIViewController, animatable: Bool = true) {
|
||||
|
||||
rootController.view.frame = UIScreen.main.bounds
|
||||
|
||||
let replaceRootViewController = {
|
||||
UIApplication.shared.keyWindow?.rootViewController = rootController
|
||||
}
|
||||
|
||||
if animatable {
|
||||
UIView.transition(
|
||||
with: UIApplication.shared.keyWindow ?? UIWindow(),
|
||||
duration: 0.5,
|
||||
options: UIView.AnimationOptions.transitionCrossDissolve,
|
||||
animations: {
|
||||
replaceRootViewController()
|
||||
}, completion: nil)
|
||||
} else {
|
||||
replaceRootViewController()
|
||||
}
|
||||
}
|
||||
|
||||
static func set(elementsColor: UIColor) {
|
||||
UINavigationController.elementsColor = elementsColor
|
||||
UIAlertController.elementsColor = elementsColor
|
||||
UITabBarController.elementsColor = elementsColor
|
||||
UITabBar.appearance().tintColor = elementsColor
|
||||
}
|
||||
}
|
||||
@@ -21,25 +21,24 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct SPLaunch {
|
||||
extension SPApp {
|
||||
|
||||
static func run() {
|
||||
self.count += 1
|
||||
}
|
||||
|
||||
static var count: Int {
|
||||
get {
|
||||
return UserDefaults.standard.value(forKey: "SPLaunchCount") as? Int ?? 0
|
||||
struct Badge {
|
||||
|
||||
static var number: Int {
|
||||
get {
|
||||
return UIApplication.shared.applicationIconBadgeNumber
|
||||
}
|
||||
set {
|
||||
UIApplication.shared.applicationIconBadgeNumber = newValue
|
||||
}
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "SPLaunchCount")
|
||||
|
||||
static func reset() {
|
||||
UIApplication.shared.applicationIconBadgeNumber = 0
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
static var isFirstOpen: Bool {
|
||||
return (self.count == 1) || (self.count == 0)
|
||||
}
|
||||
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
@@ -21,25 +21,28 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPScrollViewController: SPBaseViewController {
|
||||
extension SPApp {
|
||||
|
||||
let scrollView = SPScrollView()
|
||||
|
||||
override func viewDidLoad() {
|
||||
enum Launch {
|
||||
|
||||
self.view.backgroundColor = SPNativeStyleKit.Colors.customGray
|
||||
static func run() {
|
||||
self.count += 1
|
||||
if (UserDefaults.standard.object(forKey: "SPDateFirstLaunch") as? Date) == nil {
|
||||
UserDefaults.standard.set(Date(), forKey: "SPDateFirstLaunch")
|
||||
}
|
||||
}
|
||||
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.showsVerticalScrollIndicator = true
|
||||
self.scrollView.indicatorStyle = .black
|
||||
self.view.addSubview(self.scrollView)
|
||||
static var count: Int {
|
||||
get { return UserDefaults.standard.value(forKey: "SPLaunchCount") as? Int ?? 0 }
|
||||
set { UserDefaults.standard.set(newValue, forKey: "SPLaunchCount") }
|
||||
}
|
||||
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
override func updateLayout(with size: CGSize) {
|
||||
self.scrollView.frame = CGRect.init(origin: .zero, size: size)
|
||||
self.scrollView.contentSize = CGSize.init(width: size.width, height: size.height * 2)
|
||||
super.updateLayout(with: size)
|
||||
static var isFirstLaunch: Bool {
|
||||
return (self.count == 1) || (self.count == 0)
|
||||
}
|
||||
|
||||
static var dateFirstLaunch: Date {
|
||||
return ((UserDefaults.standard.object(forKey: "SPDateFirstLaunch") as? Date) ?? Date())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import 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) {
|
||||
|
||||
guard let url = URL(string: link) else {
|
||||
print("SPOpener - can not create URL")
|
||||
return
|
||||
}
|
||||
|
||||
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
|
||||
}
|
||||
|
||||
static func open(link: String, on controller: UIViewController) {
|
||||
guard let url = URL(string: link) else {
|
||||
print("SPOpener - can not create URL")
|
||||
return
|
||||
}
|
||||
let safariController = SFSafariViewController.init(url: url)
|
||||
controller.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)})
|
||||
}
|
||||
@@ -25,11 +25,11 @@ import StoreKit
|
||||
struct SPAppStore {
|
||||
|
||||
static func link(appID: String) -> String {
|
||||
return "https://itunes.apple.com/by/app/id" + appID
|
||||
return "https://itunes.apple.com/app/id" + appID
|
||||
}
|
||||
|
||||
static func open(appID: String) {
|
||||
if let url = URL(string: "itms-apps://itunes.apple.com/app/id\(appID)"),
|
||||
static func open(app id: String) {
|
||||
if let url = URL(string: "itms-apps://itunes.apple.com/app/id\(id)"),
|
||||
UIApplication.shared.canOpenURL(url) {
|
||||
if #available(iOS 10.0, *) {
|
||||
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
|
||||
@@ -56,6 +56,12 @@ struct SPAppStore {
|
||||
}
|
||||
}
|
||||
|
||||
static func requestReview() {
|
||||
if #available(iOS 10.3, *) {
|
||||
SKStoreReviewController.requestReview()
|
||||
}
|
||||
}
|
||||
|
||||
static func isUpdateAvailable(completion: @escaping (Bool)->()) {
|
||||
|
||||
guard let info = Bundle.main.infoDictionary,
|
||||
@@ -128,7 +134,6 @@ extension String {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function inserted by Swift 4.2 migrator.
|
||||
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
|
||||
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
|
||||
}
|
||||
@@ -22,21 +22,20 @@
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
public struct SPAudio {
|
||||
struct SPAudio {
|
||||
|
||||
static func notStopBackgroundMusic() {
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category(rawValue: convertFromAVAudioSessionCategory(AVAudioSession.Category.ambient)), mode: AVAudioSession.Mode.default)
|
||||
try AVAudioSession.sharedInstance().setActive(true)
|
||||
} catch {
|
||||
|
||||
print("SPAudio - notStopBackgroundMusic, error")
|
||||
}
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
// Helper function inserted by Swift 4.2 migrator.
|
||||
fileprivate func convertFromAVAudioSessionCategory(_ input: AVAudioSession.Category) -> String {
|
||||
return input.rawValue
|
||||
}
|
||||
@@ -22,12 +22,12 @@
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
public class SPAudioPlayer: NSObject, AVAudioPlayerDelegate {
|
||||
class SPAudioPlayer: NSObject, AVAudioPlayerDelegate {
|
||||
|
||||
fileprivate var player: AVAudioPlayer = AVAudioPlayer()
|
||||
fileprivate var endPlayingComplection: (()->())? = nil
|
||||
|
||||
func play(fileName: String, complection: (()->())? = nil) {
|
||||
func play(fileName: String, complection: (()->())? = nil, volume: Float = 1) {
|
||||
self.endPlayingComplection?()
|
||||
self.player = AVAudioPlayer()
|
||||
let url = Bundle.main.url(forResource: fileName, withExtension: nil)
|
||||
@@ -37,7 +37,7 @@ public class SPAudioPlayer: NSObject, AVAudioPlayerDelegate {
|
||||
}
|
||||
do {
|
||||
self.player = try AVAudioPlayer(contentsOf: url!)
|
||||
player.volume = 1
|
||||
player.volume = volume
|
||||
player.delegate = self
|
||||
player.prepareToPlay()
|
||||
player.play()
|
||||
@@ -51,7 +51,7 @@ public class SPAudioPlayer: NSObject, AVAudioPlayerDelegate {
|
||||
player.stop()
|
||||
}
|
||||
|
||||
public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
|
||||
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
|
||||
self.endPlayingComplection?()
|
||||
}
|
||||
}
|
||||
@@ -23,9 +23,9 @@ import UIKit
|
||||
|
||||
extension SPCodeDraw {
|
||||
|
||||
public class AudioIconPack : NSObject {
|
||||
class AudioIconPack : NSObject {
|
||||
|
||||
@objc dynamic public class func drawPlay(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawPlay(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -53,7 +53,7 @@ extension SPCodeDraw {
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawPause(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawPause(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -90,7 +90,7 @@ extension SPCodeDraw {
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawStop(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawStop(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -118,13 +118,13 @@ extension SPCodeDraw {
|
||||
}
|
||||
|
||||
@objc(StyleKitNameResizingBehavior)
|
||||
public enum ResizingBehavior: Int {
|
||||
enum ResizingBehavior: Int {
|
||||
case aspectFit /// The content is proportionally resized to fit into the target rectangle.
|
||||
case aspectFill /// The content is proportionally resized to completely fill the target rectangle.
|
||||
case stretch /// The content is stretched to match the entire target rectangle.
|
||||
case center /// The content is centered in the target rectangle, but it is NOT resized.
|
||||
|
||||
public func apply(rect: CGRect, target: CGRect) -> CGRect {
|
||||
func apply(rect: CGRect, target: CGRect) -> CGRect {
|
||||
if rect == target || target == CGRect.zero {
|
||||
return rect
|
||||
}
|
||||
@@ -21,4 +21,4 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct SPCodeDraw { private init(){} }
|
||||
struct SPCodeDraw { private init() {} }
|
||||
@@ -23,9 +23,9 @@ import UIKit
|
||||
|
||||
extension SPCodeDraw {
|
||||
|
||||
public class SocialIconPack : NSObject {
|
||||
class SocialIconPack : NSObject {
|
||||
|
||||
@objc dynamic public class func drawInstagram(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawInstagram(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -115,7 +115,7 @@ extension SPCodeDraw {
|
||||
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawVK(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawVK(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
//// General Declarations
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -167,7 +167,7 @@ extension SPCodeDraw {
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawWhatsapp(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawWhatsapp(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
//// General Declarations
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -237,7 +237,7 @@ extension SPCodeDraw {
|
||||
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawTelegram(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawTelegram(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
//// General Declarations
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -313,7 +313,7 @@ extension SPCodeDraw {
|
||||
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawFacebook(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 41, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawFacebook(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 41, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
//// General Declarations
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -352,7 +352,7 @@ extension SPCodeDraw {
|
||||
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawViber(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 41, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawViber(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 41, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
|
||||
//// General Declarations
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -443,13 +443,13 @@ extension SPCodeDraw {
|
||||
}
|
||||
|
||||
@objc(SocialIconStyleKitResizingBehavior)
|
||||
public enum ResizingBehavior: Int {
|
||||
enum ResizingBehavior: Int {
|
||||
case aspectFit /// The content is proportionally resized to fit into the target rectangle.
|
||||
case aspectFill /// The content is proportionally resized to completely fill the target rectangle.
|
||||
case stretch /// The content is stretched to match the entire target rectangle.
|
||||
case center /// The content is centered in the target rectangle, but it is NOT resized.
|
||||
|
||||
public func apply(rect: CGRect, target: CGRect) -> CGRect {
|
||||
func apply(rect: CGRect, target: CGRect) -> CGRect {
|
||||
if rect == target || target == CGRect.zero {
|
||||
return rect
|
||||
}
|
||||
@@ -23,15 +23,15 @@ import UIKit
|
||||
|
||||
extension SPCodeDraw {
|
||||
|
||||
public class SystemIconPack : NSObject {
|
||||
class SystemIconPack : NSObject {
|
||||
|
||||
private struct Cache {
|
||||
static let gradient: CGGradient = CGGradient(colorsSpace: nil, colors: [UIColor.red.cgColor, UIColor.red.cgColor] as CFArray, locations: [0, 1])!
|
||||
}
|
||||
|
||||
@objc dynamic public class var gradient: CGGradient { return Cache.gradient }
|
||||
@objc dynamic class var gradient: CGGradient { return Cache.gradient }
|
||||
|
||||
@objc dynamic public class func drawFavorite(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.478, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawFavorite(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.478, blue: 1.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -80,7 +80,7 @@ extension SPCodeDraw {
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawFavoriteFill(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.478, blue: 1.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawFavoriteFill(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.478, blue: 1.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -113,7 +113,7 @@ extension SPCodeDraw {
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawShare(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawShare(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -154,7 +154,7 @@ extension SPCodeDraw {
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
@objc dynamic public class func drawClose(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
@objc dynamic class func drawClose(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
@@ -192,13 +192,13 @@ extension SPCodeDraw {
|
||||
}
|
||||
|
||||
@objc(StyleKitNameResizingBehavior)
|
||||
public enum ResizingBehavior: Int {
|
||||
enum ResizingBehavior: Int {
|
||||
case aspectFit /// The content is proportionally resized to fit into the target rectangle.
|
||||
case aspectFill /// The content is proportionally resized to completely fill the target rectangle.
|
||||
case stretch /// The content is stretched to match the entire target rectangle.
|
||||
case center /// The content is centered in the target rectangle, but it is NOT resized.
|
||||
|
||||
public func apply(rect: CGRect, target: CGRect) -> CGRect {
|
||||
func apply(rect: CGRect, target: CGRect) -> CGRect {
|
||||
if rect == target || target == CGRect.zero {
|
||||
return rect
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
struct SPConstraints {
|
||||
|
||||
static func setEqualSizeSuperview(for view: UIView) {
|
||||
if let superView = view.superview {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
view.topAnchor.constraint(equalTo: superView.topAnchor),
|
||||
view.leftAnchor.constraint(equalTo: superView.leftAnchor),
|
||||
view.rightAnchor.constraint(equalTo: superView.rightAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: superView.bottomAnchor)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public func delay(_ delay:Double, closure:@escaping ()->()) {
|
||||
func delay(_ delay:Double, closure: @escaping ()->()) {
|
||||
let when = DispatchTime.now() + delay
|
||||
DispatchQueue.main.asyncAfter(deadline: when) {
|
||||
closure()
|
||||
@@ -23,12 +23,12 @@ import UIKit
|
||||
|
||||
struct SPDevice {
|
||||
|
||||
static var isIphone: Bool {
|
||||
return UIDevice.current.isIphone
|
||||
static var iphone: Bool {
|
||||
return UIDevice.current.userInterfaceIdiom == .phone
|
||||
}
|
||||
|
||||
static var isIpad: Bool {
|
||||
return UIDevice.current.isIpad
|
||||
static var ipad: Bool {
|
||||
return UIDevice.current.userInterfaceIdiom == .pad
|
||||
}
|
||||
|
||||
struct Orientation {
|
||||
@@ -30,6 +30,7 @@ struct SPDownloader {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
URLSession.shared.dataTask(with: url) { (data, response, error) in
|
||||
guard
|
||||
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
|
||||
@@ -47,6 +48,8 @@ struct SPDownloader {
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,8 @@ import Foundation
|
||||
|
||||
extension Array {
|
||||
|
||||
func takeElements(count: Int) -> Array {
|
||||
if (count < self.count) {
|
||||
return Array(self[0..<count])
|
||||
}
|
||||
func get(count: Int) -> Array {
|
||||
if (count < self.count) { return Array(self[0..<count]) }
|
||||
return Array(self)
|
||||
}
|
||||
}
|
||||
@@ -35,11 +33,8 @@ extension Array where Element: Equatable {
|
||||
|
||||
mutating func removeDuplicates() {
|
||||
var result = [Element]()
|
||||
|
||||
for value in self {
|
||||
if result.contains(value) == false {
|
||||
result.append(value)
|
||||
}
|
||||
if result.contains(value) == false { result.append(value) }
|
||||
}
|
||||
self = result
|
||||
}
|
||||
@@ -48,9 +43,7 @@ extension Array where Element: Equatable {
|
||||
extension Array where Element: Hashable {
|
||||
|
||||
func after(item: Element) -> Element? {
|
||||
if let index = self.index(of: item), index + 1 < self.count {
|
||||
return self[index + 1]
|
||||
}
|
||||
if let index = self.firstIndex(of: item), index + 1 < self.count { return self[index + 1] }
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
extension CGRect {
|
||||
|
||||
var bottomX: CGFloat {
|
||||
get { return self.origin.x + self.width }
|
||||
set { self.origin.x = newValue - self.width }
|
||||
}
|
||||
|
||||
var bottomY: CGFloat {
|
||||
get { return self.origin.y + self.height }
|
||||
set { self.origin.y = newValue - self.height }
|
||||
}
|
||||
|
||||
var minSide: CGFloat {
|
||||
return min(self.width, self.height)
|
||||
}
|
||||
|
||||
mutating func set(width: CGFloat) {
|
||||
self = CGRect.init(x: self.origin.x, y: self.origin.y, width: width, height: self.height)
|
||||
}
|
||||
|
||||
mutating func set(height: CGFloat) {
|
||||
self = CGRect.init(x: self.origin.x, y: self.origin.y, width: self.width, height: height)
|
||||
}
|
||||
|
||||
mutating func set(width: CGFloat, height: CGFloat) {
|
||||
self = CGRect.init(x: self.origin.x, y: self.origin.y, width: width, height: height)
|
||||
}
|
||||
}
|
||||
@@ -23,15 +23,15 @@ import UIKit
|
||||
|
||||
extension CGSize {
|
||||
|
||||
func resize(newWidth: CGFloat) -> CGSize {
|
||||
func resize(width: CGFloat) -> CGSize {
|
||||
let relativeSideSize = self.width / self.height
|
||||
let newHeight = newWidth / relativeSideSize
|
||||
return CGSize.init(width: newWidth, height: newHeight)
|
||||
let newHeight = width / relativeSideSize
|
||||
return CGSize.init(width: width, height: newHeight)
|
||||
}
|
||||
|
||||
func resize(newHeight: CGFloat) -> CGSize {
|
||||
func resize(height: CGFloat) -> CGSize {
|
||||
let relativeSideSize = self.width / self.height
|
||||
let newWidth = newHeight * relativeSideSize
|
||||
return CGSize.init(width: newWidth, height: newHeight)
|
||||
let newWidth = height * relativeSideSize
|
||||
return CGSize.init(width: newWidth, height: height)
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,20 @@ import Foundation
|
||||
|
||||
extension Date {
|
||||
|
||||
func formatted(as dateFormat: String) -> String {
|
||||
|
||||
func format(mask: String) -> String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = dateFormat
|
||||
dateFormatter.dateFormat = mask
|
||||
return dateFormatter.string(from: self)
|
||||
}
|
||||
|
||||
static func create(from value: String, mask: String = "dd.MM.yyyy HH:mm") -> Date? {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = mask
|
||||
let date = formatter.date(from: value)
|
||||
return date
|
||||
}
|
||||
|
||||
func add(days: Int) -> Date {
|
||||
return Calendar.current.date(byAdding: .day, value: days, to: self) ?? self
|
||||
}
|
||||
}
|
||||
@@ -23,13 +23,13 @@ import Foundation
|
||||
|
||||
extension Strideable {
|
||||
|
||||
public mutating func setIfMore(when value: Self) {
|
||||
mutating func setIfMore(when value: Self) {
|
||||
if self > value {
|
||||
self = value
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func setIfFewer(when value: Self) {
|
||||
mutating func setIfFewer(when value: Self) {
|
||||
if self < value {
|
||||
self = value
|
||||
}
|
||||
@@ -24,6 +24,10 @@ import UIKit
|
||||
|
||||
extension String {
|
||||
|
||||
var digits: String {
|
||||
return components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
|
||||
}
|
||||
|
||||
mutating func dropLast(substring: String) {
|
||||
if self.hasSuffix(substring) {
|
||||
self = String(dropLast(substring.count))
|
||||
@@ -66,21 +70,32 @@ extension String {
|
||||
return false
|
||||
}
|
||||
|
||||
mutating func reduce(minimumFractionDigits: Int = 0, maximumFractionDigits: Int) {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.minimumFractionDigits = minimumFractionDigits
|
||||
formatter.maximumFractionDigits = maximumFractionDigits
|
||||
let int = Double(self)
|
||||
if int != nil {
|
||||
let number = NSNumber.init(value: int!)
|
||||
if var newValue = formatter.string(from: number) {
|
||||
newValue.replace(",", with: ".")
|
||||
self = newValue
|
||||
}
|
||||
}
|
||||
var isEmptyContent: Bool {
|
||||
return (self.removeAllSpaces() == "")
|
||||
}
|
||||
|
||||
var words: [String] {
|
||||
return components(separatedBy: .punctuationCharacters).joined().components(separatedBy: .whitespaces)
|
||||
}
|
||||
|
||||
func removePrefix(_ prefix: String) -> String {
|
||||
guard self.hasPrefix(prefix) else { return self }
|
||||
return String(self.dropFirst(prefix.count))
|
||||
}
|
||||
|
||||
mutating func replace(_ replacingString: String, with newString: String) {
|
||||
self = self.replacingOccurrences(of: replacingString, with: newString)
|
||||
}
|
||||
|
||||
func replace(_ replacingString: String, with newString: String) -> String {
|
||||
return self.replacingOccurrences(of: replacingString, with: newString)
|
||||
}
|
||||
|
||||
func slice(from: String, to: String) -> String? {
|
||||
return (range(of: from)?.upperBound).flatMap { substringFrom in
|
||||
(range(of: to, range: substringFrom..<endIndex)?.lowerBound).map { substringTo in
|
||||
String(self[substringFrom..<substringTo])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,16 +32,17 @@ extension UIAlertController {
|
||||
}
|
||||
}
|
||||
|
||||
public static func show(title: String, message: String, buttonTitle: String, cancelButtonTitle: String? = nil, complection: @escaping ()->() = {}, on viewController: UIViewController) {
|
||||
|
||||
static func show(title: String? = nil, message: String? = nil, buttonTitle: String, cancelButtonTitle: String? = nil, complection: @escaping ()->() = {}, on viewController: UIViewController) {
|
||||
let ac = UIAlertController(
|
||||
title: title,
|
||||
message: message,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
|
||||
if cancelButtonTitle != nil {
|
||||
if let cancelTitle = cancelButtonTitle {
|
||||
ac.addAction(UIAlertAction.init(
|
||||
title: cancelButtonTitle!,
|
||||
title: cancelTitle,
|
||||
style: UIAlertAction.Style.cancel,
|
||||
handler: nil)
|
||||
)
|
||||
@@ -57,17 +58,15 @@ extension UIAlertController {
|
||||
viewController.present(ac, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
public static func сonfirm(title: String? = nil, message: String, buttonTitle: String, cancelButtonTitle: String, isDestructive: Bool = false, complection: @escaping (Bool)->(), on viewController: UIViewController) {
|
||||
static func сonfirm(title: String? = nil, message: String? = nil, buttonTitle: String, cancelButtonTitle: String, isDestructive: Bool = false, complection: @escaping (Bool)->(), on viewController: UIViewController) {
|
||||
let ac = UIAlertController(
|
||||
title: title,
|
||||
message: message,
|
||||
preferredStyle: .actionSheet
|
||||
)
|
||||
|
||||
var style = UIAlertAction.Style.default
|
||||
if isDestructive {
|
||||
style = .destructive
|
||||
}
|
||||
let style = isDestructive ? .destructive : UIAlertAction.Style.default
|
||||
|
||||
ac.addAction(UIAlertAction.init(
|
||||
title: buttonTitle,
|
||||
style: style,
|
||||
@@ -76,11 +75,10 @@ extension UIAlertController {
|
||||
}))
|
||||
ac.addAction(UIAlertAction.init(
|
||||
title: cancelButtonTitle,
|
||||
style: UIAlertAction.Style.default,
|
||||
style: UIAlertAction.Style.cancel,
|
||||
handler: { (action) in
|
||||
complection(false)
|
||||
}))
|
||||
|
||||
viewController.present(ac, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
@@ -100,4 +98,11 @@ extension UIAlertController {
|
||||
}
|
||||
self.addAction(action)
|
||||
}
|
||||
|
||||
func addCancelAction(title: String, complection: @escaping ()->() = {}) {
|
||||
let action = UIAlertAction(title: title, style: .cancel) { (action) in
|
||||
complection()
|
||||
}
|
||||
self.addAction(action)
|
||||
}
|
||||
}
|
||||
@@ -60,12 +60,21 @@ extension UIButton {
|
||||
|
||||
extension UIButton {
|
||||
|
||||
func setTitle(_ title: String) {
|
||||
func setTitle(_ title: String, color: UIColor? = nil) {
|
||||
self.setTitle(title, for: .normal)
|
||||
if let color = color {
|
||||
self.setTitleColor(color)
|
||||
}
|
||||
}
|
||||
|
||||
func setTitleColor(_ color: UIColor) {
|
||||
self.setTitleColorForNoramlAndHightlightedStates(color: color)
|
||||
self.setTitleColor(color, for: .normal)
|
||||
self.setTitleColor(color.withAlphaComponent(0.7), for: .highlighted)
|
||||
}
|
||||
|
||||
func setImage(_ image: UIImage) {
|
||||
self.setImage(image, for: .normal)
|
||||
self.imageView?.contentMode = .scaleAspectFit
|
||||
}
|
||||
|
||||
func removeAllTargets() {
|
||||
@@ -74,23 +83,18 @@ extension UIButton {
|
||||
|
||||
func showText(_ text: String, withComplection completion: (() -> Void)! = {}) {
|
||||
let baseText = self.titleLabel?.text ?? " "
|
||||
|
||||
SPAnimation.animate(0.2,
|
||||
animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
SPAnimation.animate(0.2, animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
}, withComplection: {
|
||||
self.setTitle(text, for: .normal)
|
||||
|
||||
SPAnimation.animate(0.2, animations: {
|
||||
self.titleLabel?.alpha = 1
|
||||
}, withComplection: {
|
||||
|
||||
SPAnimation.animate(0.2, animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
}, delay: 0.35,
|
||||
withComplection: {
|
||||
self.setTitle(baseText, for: .normal)
|
||||
|
||||
SPAnimation.animate(0.2, animations: {
|
||||
self.titleLabel?.alpha = 1
|
||||
}, withComplection: {
|
||||
@@ -102,14 +106,11 @@ extension UIButton {
|
||||
}
|
||||
|
||||
func setAnimatableText(_ text: String, withComplection completion: (() -> Void)! = {}) {
|
||||
|
||||
SPAnimation.animate(0.2,
|
||||
animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
SPAnimation.animate(0.3, animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
}, withComplection: {
|
||||
self.setTitle(text, for: .normal)
|
||||
|
||||
SPAnimation.animate(0.2, animations: {
|
||||
SPAnimation.animate(0.3, animations: {
|
||||
self.titleLabel?.alpha = 1
|
||||
}, withComplection: {
|
||||
completion()
|
||||
@@ -118,25 +119,18 @@ extension UIButton {
|
||||
}
|
||||
|
||||
func hideContent(completion: (() -> Void)! = {}) {
|
||||
SPAnimation.animate(0.2,
|
||||
animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
SPAnimation.animate(0.25, animations: {
|
||||
self.titleLabel?.alpha = 0
|
||||
}, withComplection: {
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
func showContent(completion: (() -> Void)! = {}) {
|
||||
SPAnimation.animate(0.2,
|
||||
animations: {
|
||||
self.titleLabel?.alpha = 1
|
||||
SPAnimation.animate(0.25, animations: {
|
||||
self.titleLabel?.alpha = 1
|
||||
}, withComplection: {
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
func setTitleColorForNoramlAndHightlightedStates(color: UIColor) {
|
||||
self.setTitleColor(color, for: .normal)
|
||||
self.setTitleColor(color.withAlphaComponent(0.7), for: .highlighted)
|
||||
}
|
||||
}
|
||||
@@ -29,4 +29,28 @@ extension UICollectionView {
|
||||
let visibleIndexPath = self.indexPathForItem(at: visiblePoint)
|
||||
return visibleIndexPath
|
||||
}
|
||||
|
||||
func lastRow(indexPath: IndexPath) -> Int {
|
||||
return self.numberOfItems(inSection: indexPath.section) - 1
|
||||
}
|
||||
|
||||
func isLastRow(indexPath: IndexPath) -> Bool {
|
||||
return indexPath.row == self.lastRow(indexPath: indexPath)
|
||||
}
|
||||
|
||||
func reloadData(animated: Bool, complection: @escaping ()->() = {}) {
|
||||
if animated {
|
||||
UIView.transition(
|
||||
with: self,
|
||||
duration: 0.3,
|
||||
options: [.transitionCrossDissolve, UIView.AnimationOptions.beginFromCurrentState],
|
||||
animations: {
|
||||
self.reloadData()
|
||||
}, completion: {(state) in
|
||||
complection()
|
||||
})
|
||||
} else {
|
||||
self.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UIColor {
|
||||
extension UIColor {
|
||||
|
||||
convenience init(hex: String) {
|
||||
var red: CGFloat = 0.0
|
||||
@@ -0,0 +1,61 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIFont {
|
||||
|
||||
static func system(weight: FontWeight, size: CGFloat) -> UIFont {
|
||||
return UIFont.systemFont(ofSize: size, weight: self.weight(for: weight))
|
||||
}
|
||||
|
||||
private static func weight(for weight: FontWeight) -> UIFont.Weight {
|
||||
switch weight {
|
||||
case .ultraLight:
|
||||
return UIFont.Weight.ultraLight
|
||||
case .light:
|
||||
return UIFont.Weight.light
|
||||
case .medium:
|
||||
return UIFont.Weight.medium
|
||||
case .regular:
|
||||
return UIFont.Weight.regular
|
||||
case .bold:
|
||||
return UIFont.Weight.bold
|
||||
case .demiBold:
|
||||
return UIFont.Weight.semibold
|
||||
case .heavy:
|
||||
return UIFont.Weight.heavy
|
||||
default:
|
||||
return UIFont.Weight.regular
|
||||
}
|
||||
}
|
||||
|
||||
enum FontWeight {
|
||||
case regular
|
||||
case medium
|
||||
case light
|
||||
case ultraLight
|
||||
case heavy
|
||||
case bold
|
||||
case demiBold
|
||||
case none
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIImage {
|
||||
|
||||
func resize(width: CGFloat) -> UIImage {
|
||||
let scale = width / self.size.width
|
||||
let newHeight = self.size.height * scale
|
||||
UIGraphicsBeginImageContext(CGSize(width: width, height: newHeight))
|
||||
self.draw(in: CGRect(x: 0, y: 0, width: width, height: newHeight))
|
||||
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return newImage!
|
||||
}
|
||||
|
||||
var alwaysOriginal: UIImage {
|
||||
return self.withRenderingMode(.alwaysOriginal)
|
||||
}
|
||||
|
||||
var alwaysTemplate: UIImage {
|
||||
return self.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
}
|
||||
@@ -23,18 +23,13 @@ import UIKit
|
||||
|
||||
extension UIImageView {
|
||||
|
||||
public func setNativeStyle() {
|
||||
func setNative() {
|
||||
self.layer.borderWidth = 0.5
|
||||
self.layer.borderColor = SPNativeStyleKit.Colors.midGray.cgColor
|
||||
self.layer.borderColor = SPNativeColors.midGray.cgColor
|
||||
self.layer.masksToBounds = true
|
||||
}
|
||||
|
||||
public func removeNativeStyle() {
|
||||
self.layer.borderWidth = 0
|
||||
self.layer.borderColor = UIColor.clear.cgColor
|
||||
}
|
||||
|
||||
public func downloadedFrom(url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit, withComplection complection: @escaping (UIImage?) -> () = {_ in }) {
|
||||
func downloadedFrom(url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit, withComplection complection: @escaping (UIImage?) -> () = {_ in }) {
|
||||
DispatchQueue.main.async {
|
||||
self.contentMode = mode
|
||||
}
|
||||
@@ -52,7 +47,7 @@ extension UIImageView {
|
||||
}.resume()
|
||||
}
|
||||
|
||||
public func downloadedFrom(link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit, withComplection complection: @escaping (UIImage?) -> () = {_ in }) {
|
||||
func downloadedFrom(link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit, withComplection complection: @escaping (UIImage?) -> () = {_ in }) {
|
||||
guard let url = URL(string: link) else { return }
|
||||
downloadedFrom(url: url, contentMode: mode, withComplection: complection)
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UILabel {
|
||||
extension UILabel {
|
||||
|
||||
func setShadowOffsetForLetters(blurRadius: CGFloat = 0, widthOffset: CGFloat = 0, heightOffset: CGFloat, opacity: CGFloat) {
|
||||
self.layer.shadowRadius = blurRadius
|
||||
@@ -42,7 +42,7 @@ public extension UILabel {
|
||||
self.setShadowOffsetForLetters(blurRadius: 0, widthOffset: 0, heightOffset: 0, opacity: 0)
|
||||
}
|
||||
|
||||
func setCenteringAlignment() {
|
||||
func setCenterAlignment() {
|
||||
self.textAlignment = .center
|
||||
self.baselineAdjustment = .alignCenters
|
||||
}
|
||||
@@ -53,4 +53,48 @@ public extension UILabel {
|
||||
attributedText = NSAttributedString(string: textString, attributes: attrs)
|
||||
}
|
||||
}
|
||||
|
||||
func setLineSpacing(_ lineSpacing: CGFloat = 0.0, lineHeightMultiple: CGFloat = 0.0) {
|
||||
|
||||
guard let labelText = self.text else { return }
|
||||
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
paragraphStyle.lineSpacing = lineSpacing
|
||||
paragraphStyle.lineHeightMultiple = lineHeightMultiple
|
||||
|
||||
let attributedString: NSMutableAttributedString
|
||||
if let labelattributedText = self.attributedText {
|
||||
attributedString = NSMutableAttributedString(attributedString: labelattributedText)
|
||||
} else {
|
||||
attributedString = NSMutableAttributedString(string: labelText)
|
||||
}
|
||||
|
||||
attributedString.addAttribute(NSAttributedString.Key.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
|
||||
|
||||
self.attributedText = attributedString
|
||||
}
|
||||
|
||||
func setFormat(text: String, positions: [FormatPosition], font: UIFont, textColor: UIColor, backgroundColor: UIColor = .clear) {
|
||||
|
||||
let title = NSMutableAttributedString.init(string: text)
|
||||
|
||||
for position in positions {
|
||||
title.addAttributes(
|
||||
[
|
||||
NSAttributedString.Key.backgroundColor : backgroundColor,
|
||||
NSAttributedString.Key.foregroundColor : textColor,
|
||||
NSAttributedString.Key.font : font
|
||||
],
|
||||
range: NSRange.init(location: position.start, length: position.length)
|
||||
)
|
||||
}
|
||||
|
||||
self.attributedText = title
|
||||
}
|
||||
|
||||
struct FormatPosition {
|
||||
|
||||
var start: Int
|
||||
var length: Int
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,11 @@ extension UINavigationController {
|
||||
|
||||
static var elementsColor: UIColor {
|
||||
get {
|
||||
return UINavigationBar.appearance().tintColor
|
||||
if UINavigationBar.appearance().tintColor != nil {
|
||||
return UINavigationBar.appearance().tintColor
|
||||
} else {
|
||||
return SPNativeColors.blue
|
||||
}
|
||||
}
|
||||
set {
|
||||
UINavigationBar.appearance().tintColor = newValue
|
||||
@@ -19,11 +19,11 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension Collection {
|
||||
extension UIScrollView {
|
||||
|
||||
subscript (safe index: Index) -> Element? {
|
||||
return indices.contains(index) ? self[index] : nil
|
||||
func stopScrolling() {
|
||||
self.setContentOffset(self.contentOffset, animated: false)
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
@@ -36,50 +36,24 @@ extension UITableView {
|
||||
}
|
||||
|
||||
var lastSectionWithRows: Int? {
|
||||
|
||||
if self.numberOfSections == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if self.numberOfSections == 0 { return nil }
|
||||
var section = self.numberOfSections - 1
|
||||
|
||||
if section < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if section < 0 { return nil }
|
||||
while section >= 0 {
|
||||
|
||||
if self.numberOfRows(inSection: section) != 0 {
|
||||
return section
|
||||
}
|
||||
|
||||
if self.numberOfRows(inSection: section) != 0 { return section }
|
||||
section -= 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var firstSectionWithRows: Int? {
|
||||
|
||||
if self.numberOfSections == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if self.numberOfSections == 0 { return nil }
|
||||
var section = 0
|
||||
|
||||
if section > self.numberOfSections - 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if section > self.numberOfSections - 1 { return nil }
|
||||
while section <= (self.numberOfSections - 1) {
|
||||
|
||||
if self.numberOfRows(inSection: section) != 0 {
|
||||
return section
|
||||
}
|
||||
|
||||
if self.numberOfRows(inSection: section) != 0 { return section }
|
||||
section += 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import UIKit
|
||||
|
||||
extension UITableViewCell {
|
||||
|
||||
public var accessoryView: UIView? {
|
||||
var accessoryView: UIView? {
|
||||
return subviews.compactMap { $0 as? UIButton }.first
|
||||
}
|
||||
|
||||
@@ -33,12 +33,12 @@ extension UITableViewCell {
|
||||
}
|
||||
set {
|
||||
let backgroundView = UIView()
|
||||
backgroundView.backgroundColor = SPNativeStyleKit.Colors.customGray
|
||||
backgroundView.backgroundColor = SPNativeColors.customGray
|
||||
self.selectedBackgroundView = backgroundView
|
||||
}
|
||||
}
|
||||
|
||||
public func highlight() {
|
||||
func highlight() {
|
||||
self.setHighlighted(true, animated: false)
|
||||
self.setHighlighted(false, animated: true)
|
||||
}
|
||||
@@ -39,7 +39,6 @@ extension UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Keyboard
|
||||
extension UIViewController {
|
||||
|
||||
func dismissKeyboardWhenTappedAround() {
|
||||
@@ -53,16 +52,13 @@ extension UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Add image to Library
|
||||
extension UIViewController {
|
||||
|
||||
func save(image: UIImage) {
|
||||
if SPPermission.isAllow(.photoLibrary) {
|
||||
if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized {
|
||||
UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)
|
||||
} else {
|
||||
SPPermission.request(.photoLibrary) {
|
||||
UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)
|
||||
}
|
||||
print("Saving image error. Not allowed permission")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +103,6 @@ extension UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Navigation Bar
|
||||
extension UIViewController {
|
||||
|
||||
func setPrefersLargeNavigationTitle(_ title: String, smallScreenToSmallBar: Bool = true) {
|
||||
@@ -129,31 +124,40 @@ extension UIViewController {
|
||||
switch style {
|
||||
case .large:
|
||||
if #available(iOS 11.0, *) {
|
||||
self.navigationController?.navigationBar.prefersLargeTitles = true
|
||||
self.navigationItem.largeTitleDisplayMode = .always
|
||||
}
|
||||
case .small:
|
||||
if #available(iOS 11.0, *) {
|
||||
self.navigationItem.largeTitleDisplayMode = .never
|
||||
}
|
||||
}
|
||||
case .stork:
|
||||
if #available(iOS 11.0, *) {
|
||||
self.navigationItem.largeTitleDisplayMode = .never
|
||||
}
|
||||
case .noContent:
|
||||
if #available(iOS 11.0, *) {
|
||||
self.navigationItem.largeTitleDisplayMode = .never
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
|
||||
var topSafeArea: CGFloat {
|
||||
return self.view.topSafeArea
|
||||
}
|
||||
|
||||
var bottomSafeArea: CGFloat {
|
||||
return self.view.bottomSafeArea
|
||||
var safeArea: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
return self.view.safeAreaInsets
|
||||
} else {
|
||||
return UIEdgeInsets.zero
|
||||
}
|
||||
}
|
||||
|
||||
var navigationBarHeight: CGFloat {
|
||||
return self.navigationController?.navigationBar.frame.height ?? 0
|
||||
}
|
||||
|
||||
var statusBarHeight: CGFloat {
|
||||
static var statusBarHeight: CGFloat {
|
||||
return UIApplication.shared.statusBarFrame.height
|
||||
}
|
||||
}
|
||||
@@ -21,49 +21,38 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
// MARK: - layout
|
||||
public extension UIView {
|
||||
extension UIView {
|
||||
|
||||
var topSafeArea: CGFloat {
|
||||
var topSafeArea: CGFloat = 0
|
||||
if #available(iOS 11.0, *) {
|
||||
topSafeArea = self.safeAreaInsets.top
|
||||
var 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 }
|
||||
}
|
||||
return topSafeArea
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
|
||||
var bottomSafeArea: CGFloat {
|
||||
var bottomSafeArea: CGFloat = 0
|
||||
var safeArea: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
bottomSafeArea = self.safeAreaInsets.bottom
|
||||
return self.safeAreaInsets
|
||||
} else{
|
||||
return UIEdgeInsets.zero
|
||||
}
|
||||
return bottomSafeArea
|
||||
}
|
||||
|
||||
func setHeight(_ height: CGFloat) {
|
||||
self.frame = CGRect.init(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.width, height: height)
|
||||
func setBounds(_ view: UIView, withWidthFactor widthFactor: CGFloat = 1, maxWidth: CGFloat? = nil, withHeightFactor heightFactor: CGFloat = 1, maxHeight: CGFloat? = nil, withCentering: Bool = false) {
|
||||
self.setBounds(view.bounds, withWidthFactor: widthFactor, maxWidth: maxWidth, withHeightFactor: heightFactor, maxHeight: maxHeight, withCentering: withCentering)
|
||||
}
|
||||
|
||||
func setWidth(_ width: CGFloat) {
|
||||
self.frame = CGRect.init(x: self.frame.origin.x, y: self.frame.origin.y, width: width, height: self.frame.height)
|
||||
}
|
||||
|
||||
func setEqualsFrameFromBounds(_ view: UIView, withWidthFactor widthFactor: CGFloat = 1, maxWidth: CGFloat? = nil, withHeightFactor heightFactor: CGFloat = 1, maxHeight: CGFloat? = nil, withCentering: Bool = false) {
|
||||
|
||||
self.setEqualsFrameFromBounds(view.bounds, withWidthFactor: widthFactor, maxWidth: maxWidth, withHeightFactor: heightFactor, maxHeight: maxHeight, withCentering: withCentering)
|
||||
}
|
||||
|
||||
func setEqualsFrameFromBounds(_ bounds: CGRect, withWidthFactor widthFactor: CGFloat = 1, maxWidth: CGFloat? = nil, withHeightFactor heightFactor: CGFloat = 1, maxHeight: CGFloat? = nil, withCentering: Bool = false) {
|
||||
func setBounds(_ bounds: CGRect, withWidthFactor widthFactor: CGFloat = 1, maxWidth: CGFloat? = nil, withHeightFactor heightFactor: CGFloat = 1, maxHeight: CGFloat? = nil, withCentering: Bool = false) {
|
||||
|
||||
var width = bounds.width * widthFactor
|
||||
if maxWidth != nil {
|
||||
width.setIfMore(when: maxWidth!)
|
||||
}
|
||||
if maxWidth != nil { width.setIfMore(when: maxWidth!) }
|
||||
|
||||
var height = bounds.height * heightFactor
|
||||
if maxHeight != nil {
|
||||
height.setIfMore(when: maxHeight!)
|
||||
}
|
||||
if maxHeight != nil { height.setIfMore(when: maxHeight!) }
|
||||
|
||||
self.frame = CGRect.init(x: 0, y: 0, width: width, height: height)
|
||||
|
||||
@@ -73,28 +62,20 @@ public extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
func setEqualsBoundsFromSuperview(customWidth: CGFloat? = nil, customHeight: CGFloat? = nil) {
|
||||
|
||||
if self.superview == nil {
|
||||
return
|
||||
}
|
||||
|
||||
func setSuperviewBounds(customWidth: CGFloat? = nil, customHeight: CGFloat? = nil) {
|
||||
if self.superview == nil { return }
|
||||
self.frame = CGRect.init(origin: CGPoint.zero, size: self.superview!.frame.size)
|
||||
|
||||
if customWidth != nil {
|
||||
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: customWidth!, height: self.frame.height))
|
||||
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: customWidth!, height: self.superview!.frame.height))
|
||||
}
|
||||
|
||||
if customHeight != nil {
|
||||
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: self.frame.width, height: customHeight!))
|
||||
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: self.superview!.frame.width, height: customHeight!))
|
||||
}
|
||||
}
|
||||
|
||||
func resize(newWidth width: CGFloat) {
|
||||
func resize(width: CGFloat) {
|
||||
let relativeFactor = self.frame.width / self.frame.height
|
||||
if relativeFactor.isNaN {
|
||||
return
|
||||
}
|
||||
if relativeFactor.isNaN { return }
|
||||
self.frame = CGRect.init(
|
||||
x: self.frame.origin.x,
|
||||
y: self.frame.origin.y,
|
||||
@@ -103,11 +84,9 @@ public extension UIView {
|
||||
)
|
||||
}
|
||||
|
||||
func resize(newHeight height: CGFloat) {
|
||||
func resize(height: CGFloat) {
|
||||
let relativeFactor = self.frame.width / self.frame.height
|
||||
if relativeFactor.isNaN {
|
||||
return
|
||||
}
|
||||
if relativeFactor.isNaN { return }
|
||||
self.frame = CGRect.init(
|
||||
x: self.frame.origin.x,
|
||||
y: self.frame.origin.y,
|
||||
@@ -116,19 +95,23 @@ public extension UIView {
|
||||
)
|
||||
}
|
||||
|
||||
func setXCenteringFromSuperview() {
|
||||
func setYCenter() {
|
||||
self.center.y = (self.superview?.frame.height ?? 0) / 2
|
||||
}
|
||||
|
||||
func setXCenter() {
|
||||
self.center.x = (self.superview?.frame.width ?? 0) / 2
|
||||
}
|
||||
|
||||
func setToCenterInSuperview() {
|
||||
func setToCenter() {
|
||||
self.center = CGPoint.init(x: ((self.superview?.frame.width) ?? 0) / 2, y: ((self.superview?.frame.height) ?? 0) / 2)
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIView {
|
||||
extension UIView {
|
||||
|
||||
func setParalax(amountFactor: CGFloat) {
|
||||
let amount = self.frame.minSideSize * amountFactor
|
||||
let amount = self.frame.minSide * amountFactor
|
||||
self.setParalax(amount: amount)
|
||||
}
|
||||
|
||||
@@ -148,29 +131,19 @@ public extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - convertToImage
|
||||
public extension UIView {
|
||||
|
||||
func convertToImage() -> UIImage {
|
||||
return UIImage.drawFromView(view: self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - gradeView
|
||||
public extension UIView {
|
||||
extension UIView {
|
||||
|
||||
func addGrade(alpha: CGFloat, color: UIColor = UIColor.black) -> UIView {
|
||||
let gradeView = UIView.init()
|
||||
gradeView.alpha = 0
|
||||
self.addSubview(gradeView)
|
||||
SPConstraintsAssistent.setEqualSizeConstraint(gradeView, superVuew: self)
|
||||
SPConstraints.setEqualSizeSuperview(for: gradeView)
|
||||
gradeView.alpha = alpha
|
||||
gradeView.backgroundColor = color
|
||||
return gradeView
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - shadow
|
||||
extension UIView {
|
||||
|
||||
func setShadow(
|
||||
@@ -188,14 +161,14 @@ extension UIView {
|
||||
let xTranslation = (self.frame.width - shadowWidth) / 2 + (self.frame.width * xTranslationFactor)
|
||||
let yTranslation = (self.frame.height - shadowHeight) / 2 + (self.frame.height * yTranslationFactor)
|
||||
|
||||
let cornerRadius = self.frame.minSideSize * cornerRadiusFactor
|
||||
let cornerRadius = self.frame.minSide * cornerRadiusFactor
|
||||
|
||||
let shadowPath = UIBezierPath.init(
|
||||
roundedRect: CGRect.init(x: xTranslation, y: yTranslation, width: shadowWidth, height: shadowHeight),
|
||||
cornerRadius: cornerRadius
|
||||
)
|
||||
|
||||
let blurRadius = self.frame.minSideSize * blurRadiusFactor
|
||||
let blurRadius = self.frame.minSide * blurRadiusFactor
|
||||
|
||||
self.layer.shadowColor = UIColor.black.cgColor
|
||||
self.layer.shadowOffset = CGSize.zero
|
||||
@@ -249,7 +222,6 @@ extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - animation
|
||||
extension UIView {
|
||||
|
||||
func addCornerRadiusAnimation(to: CGFloat, duration: CFTimeInterval) {
|
||||
@@ -276,9 +248,12 @@ extension UIView {
|
||||
self.isHidden = true
|
||||
})
|
||||
}
|
||||
|
||||
func removeAllAnimations() {
|
||||
self.layer.removeAllAnimations()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - corner radius
|
||||
extension UIView {
|
||||
|
||||
func roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
|
||||
@@ -289,6 +264,6 @@ extension UIView {
|
||||
}
|
||||
|
||||
func round() {
|
||||
self.layer.cornerRadius = self.frame.minSideSize / 2
|
||||
self.layer.cornerRadius = self.frame.minSide / 2
|
||||
}
|
||||
}
|
||||
@@ -21,15 +21,16 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct SPApp {
|
||||
extension UIVisualEffectView {
|
||||
|
||||
static var udid: String? {
|
||||
return UIDevice.current.identifierForVendor?.uuidString
|
||||
convenience init(style: UIBlurEffect.Style) {
|
||||
let effect = UIBlurEffect(style: style)
|
||||
self.init(effect: effect)
|
||||
}
|
||||
|
||||
static var displayName: String? {
|
||||
return Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
|
||||
convenience init(vibrancy style: UIBlurEffect.Style) {
|
||||
let effect = UIBlurEffect(style: style)
|
||||
let vibrancyEffect = UIVibrancyEffect(blurEffect: effect)
|
||||
self.init(effect: vibrancyEffect)
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
@@ -21,22 +21,34 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPLayout {
|
||||
struct SPLayout {
|
||||
|
||||
static func sizeWith(widthFactor: CGFloat, maxWidth: CGFloat, heightFactor: CGFloat, maxHeight: CGFloat, relativeSideFactor: CGFloat, from relativeSize: CGSize) -> CGSize {
|
||||
static func sizeWith(widthFactor: CGFloat, maxWidth: CGFloat?, heightFactor: CGFloat, maxHeight: CGFloat?, relativeSideFactor: CGFloat?, from size: CGSize) -> CGSize {
|
||||
|
||||
var widthArea = relativeSize.width * widthFactor
|
||||
var heightArea = relativeSize.height * heightFactor
|
||||
var widthArea = size.width * widthFactor
|
||||
var heightArea = size.height * heightFactor
|
||||
|
||||
widthArea.setIfMore(when: maxWidth)
|
||||
heightArea.setIfMore(when: maxHeight)
|
||||
if let maxWidth = maxWidth {
|
||||
widthArea.setIfMore(when: maxWidth)
|
||||
}
|
||||
|
||||
if let maxHeight = maxHeight {
|
||||
heightArea.setIfMore(when: maxHeight)
|
||||
}
|
||||
|
||||
var prepareWidth = widthArea
|
||||
var prepareHeight = widthArea / relativeSideFactor
|
||||
if prepareHeight > heightArea {
|
||||
prepareHeight = heightArea
|
||||
prepareWidth = heightArea * relativeSideFactor
|
||||
var prepareHeight = heightArea
|
||||
|
||||
if let relativeSideFactor = relativeSideFactor {
|
||||
prepareHeight = widthArea / relativeSideFactor
|
||||
if prepareHeight > heightArea {
|
||||
prepareHeight = heightArea
|
||||
prepareWidth = heightArea * relativeSideFactor
|
||||
}
|
||||
}
|
||||
|
||||
return CGSize.init(width: prepareWidth, height: prepareHeight)
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
@@ -20,32 +20,22 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import LocalAuthentication
|
||||
|
||||
extension CGRect {
|
||||
struct SPLocalAuthentication {
|
||||
|
||||
var bottomXPosition: CGFloat {
|
||||
get {
|
||||
return self.origin.x + self.width
|
||||
}
|
||||
set {
|
||||
self.origin.x = newValue - self.width
|
||||
static var isEnable: Bool {
|
||||
let context = LAContext()
|
||||
var error: NSError?
|
||||
return context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error)
|
||||
}
|
||||
|
||||
static func request(reason: String, complecton: @escaping (Bool)->()) {
|
||||
let context = LAContext()
|
||||
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, error in
|
||||
DispatchQueue.main.async { complecton(success) }
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
var isWidthLessThanHeight: Bool {
|
||||
return self.width < self.height
|
||||
}
|
||||
private init() {}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ struct SPMail {
|
||||
return MFMailComposeViewController.canSendMail()
|
||||
}
|
||||
|
||||
static func openMailApp(to email: String, subject: String? = nil, body: String? = nil) {
|
||||
static func openApp(to email: String, subject: String? = nil, body: String? = nil) {
|
||||
let parametrs = "mailto:\(email)?subject=\(subject ?? "")&body=\(body ?? "")".addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
|
||||
|
||||
if parametrs != nil {
|
||||
@@ -40,7 +40,7 @@ struct SPMail {
|
||||
}
|
||||
}
|
||||
|
||||
static func mailDialog(to email: String, subject: String? = nil, body: String? = nil, on viewController: UIViewController) {
|
||||
static func dialog(to email: String, subject: String? = nil, body: String? = nil, on viewController: UIViewController) {
|
||||
let mailVC = MFMailComposeViewController()
|
||||
mailVC.mailComposeDelegate = SPMailSingltone.sharedInstance
|
||||
|
||||
@@ -69,7 +69,6 @@ struct SPMail {
|
||||
private init() {}
|
||||
}
|
||||
|
||||
// Helper function inserted by Swift 4.2 migrator.
|
||||
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
|
||||
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
enum SPNativeColors {
|
||||
|
||||
static let red = UIColor.init(hex: "FF3B30")
|
||||
static let orange = UIColor.init(hex: "FF9500")
|
||||
static let yellow = UIColor.init(hex: "FFCC00")
|
||||
static let green = UIColor.init(hex: "4CD964")
|
||||
static let tealBlue = UIColor.init(hex: "5AC8FA")
|
||||
static let blue = UIColor.init(hex: "007AFF")
|
||||
static let purple = UIColor.init(hex: "5856D6")
|
||||
static let pink = UIColor.init(hex: "FF2D55")
|
||||
static let white = UIColor.init(hex: "FFFFFF")
|
||||
static let customGray = UIColor.init(hex: "EFEFF4")
|
||||
static let lightGray = UIColor.init(hex: "E5E5EA")
|
||||
static let lightGray2 = UIColor.init(hex: "D1D1D6")
|
||||
static let midGray = UIColor.init(hex: "C7C7CC")
|
||||
static let gray = UIColor.init(hex: "8E8E93")
|
||||
static let black = UIColor.init(hex: "000000")
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// The MIT License (MIT)
|
||||
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import UserNotifications
|
||||
|
||||
struct SPLocalNotification {
|
||||
|
||||
var identificator: String? = nil
|
||||
var text: String
|
||||
var title: String? = nil
|
||||
var badge: Int = 0
|
||||
var timeInterval: TimeInterval
|
||||
var soundEnabled: Bool = true
|
||||
var category: SPLocalNotificationCategory? = nil
|
||||
|
||||
init(after timeInterval: TimeInterval, text: String) {
|
||||
self.text = text
|
||||
self.timeInterval = timeInterval
|
||||
}
|
||||
|
||||
func add() {
|
||||
let identificator = self.identificator ?? "\(self.timeInterval)\(self.text)\(Int.random(min: 0, max: 1000))"
|
||||
|
||||
let notification = UNNotificationRequest(
|
||||
identifier: identificator,
|
||||
content: self.content,
|
||||
trigger: self.trigger
|
||||
)
|
||||
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.add(notification) { (error) in
|
||||
if let error = error {
|
||||
print("SPLocalNotification - \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var content: UNMutableNotificationContent {
|
||||
let content = UNMutableNotificationContent()
|
||||
content.body = self.text
|
||||
content.title = self.title ?? ""
|
||||
content.badge = NSNumber(value: UIApplication.shared.applicationIconBadgeNumber + self.badge)
|
||||
content.sound = self.soundEnabled ? UNNotificationSound.default : nil
|
||||
|
||||
if let category = self.category {
|
||||
if #available(iOS 12.0, *) {
|
||||
let notificationCategory = UNNotificationCategory(identifier: category.identifier, actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: nil, categorySummaryFormat: category.summary, options: [])
|
||||
UNUserNotificationCenter.current().setNotificationCategories([notificationCategory])
|
||||
content.categoryIdentifier = notificationCategory.identifier
|
||||
}
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
private var trigger: UNTimeIntervalNotificationTrigger {
|
||||
return UNTimeIntervalNotificationTrigger(timeInterval: self.timeInterval, repeats: false)
|
||||
}
|
||||
}
|
||||
|
||||
struct SPLocalNotificationCategory {
|
||||
|
||||
var identifier: String
|
||||
var summary: String
|
||||
|
||||
static var countSymbol: String {
|
||||
return "%u"
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,9 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension String {
|
||||
public static func random(count: Int) -> String {
|
||||
extension String {
|
||||
|
||||
static func random(count: Int) -> String {
|
||||
let strings = [
|
||||
"В доме кардинала от меня не было тайн; не раз видел я, как он усердно перелистывает старинные книги и жадно роется в пыли фамильных рукописей. Когда я как-то упрекнул его за бесполезные бессонные ночи, после которых он впадал в болезненное уныние, он взглянул на меня с горькой улыбкой и раскрыл передо мною историю города Рима. В этой книге, в двадцатой главе жизнеописания папы Александра Шестого, я прочел следующие строки, навсегда оставшиеся в моей памяти",
|
||||
"По этому поводу между отцом и сыном завязался спор. Цезарь считал, что достаточно применить одно из тех средств, которые он всегда держал наготове для своих ближайших друзей, а именно: пресловутый ключ, которым то одного, то другого просили отпереть некий шкаф. На ключе был крохотный железный шип – недосмотр слесаря. Каждый, кто трудился над тугим замком, накалывал себе палец и на другой день умирал. Был еще перстень с львиной головой, который Цезарь надевал, когда хотел пожать руку той или иной особе. Лев впивался в кожу этих избранных рук, и через сутки наступала смерть.",
|
||||
@@ -33,58 +34,47 @@ public extension String {
|
||||
}
|
||||
}
|
||||
|
||||
public extension Bool {
|
||||
public static func random() -> Bool {
|
||||
return arc4random_uniform(2) == 0
|
||||
}
|
||||
}
|
||||
|
||||
public extension Int {
|
||||
public static func random(_ n: Int) -> Int {
|
||||
return Int(arc4random_uniform(UInt32(n)))
|
||||
extension Int {
|
||||
|
||||
static func random() -> Int {
|
||||
return Int.random(in: 0...Int.max)
|
||||
}
|
||||
|
||||
public static func random(min: Int, max: Int) -> Int {
|
||||
return Int(arc4random_uniform(UInt32(max - min - 1))) + min
|
||||
static func random(min: Int, max: Int) -> Int {
|
||||
return Int.random(in: min...max)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Double {
|
||||
public static func random() -> Double {
|
||||
return Double(arc4random()) / 0xFFFFFFFF
|
||||
extension Double {
|
||||
|
||||
static func random() -> Double {
|
||||
return Double.random(in: 0...Double.greatestFiniteMagnitude)
|
||||
}
|
||||
|
||||
public static func random(min: Double, max: Double) -> Double {
|
||||
return Double.random() * (max - min) + min
|
||||
static func random(min: Double, max: Double) -> Double {
|
||||
return Double.random(in: min...max)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Float {
|
||||
public static func random() -> Float {
|
||||
return Float(arc4random()) / 0xFFFFFFFF
|
||||
extension Float {
|
||||
|
||||
static func random() -> Float {
|
||||
return Float.random(in: 0...Float.greatestFiniteMagnitude)
|
||||
}
|
||||
|
||||
public static func random(min: Float, max: Float) -> Float {
|
||||
return Float.random() * (max - min) + min
|
||||
static func random(min: Float, max: Float) -> Float {
|
||||
return Float.random(in: min...max)
|
||||
}
|
||||
}
|
||||
|
||||
public extension CGFloat {
|
||||
public static func random() -> CGFloat {
|
||||
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
|
||||
extension CGFloat {
|
||||
|
||||
static func random() -> CGFloat {
|
||||
return CGFloat.random(in: 0...CGFloat.greatestFiniteMagnitude)
|
||||
}
|
||||
|
||||
public static func random(min: CGFloat, max: CGFloat) -> CGFloat {
|
||||
return CGFloat.random() * (max - min) + min
|
||||
}
|
||||
}
|
||||
|
||||
public extension Collection {
|
||||
/// Return a copy of `self` with its elements shuffled
|
||||
func shuffle() -> [Iterator.Element] {
|
||||
var list = Array(self)
|
||||
list.shuffleInPlace()
|
||||
return list
|
||||
static func random(min: CGFloat, max: CGFloat) -> CGFloat {
|
||||
return CGFloat.random(in: min...max)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,10 +85,10 @@ extension Collection where Index == Int {
|
||||
}
|
||||
}
|
||||
|
||||
public extension MutableCollection where Index == Int {
|
||||
/// Shuffle the elements of `self` in-place.
|
||||
extension MutableCollection where Index == Int {
|
||||
|
||||
mutating func shuffleInPlace() {
|
||||
// empty and single-element collections don't shuffle
|
||||
|
||||
if count < 2 { return }
|
||||
|
||||
for i in startIndex ..< endIndex - 1 {
|
||||
@@ -27,13 +27,13 @@ extension SPShadow {
|
||||
|
||||
private init() {}
|
||||
|
||||
public static func setFor(label: UILabel) {
|
||||
static func setFor(label: UILabel) {
|
||||
var offset = label.frame.height * 0.03
|
||||
offset.setIfMore(when: 1)
|
||||
label.setShadowOffsetForLetters(heightOffset: offset, opacity: 0.35)
|
||||
}
|
||||
|
||||
public static func setFor(view: UIView) {
|
||||
static func setFor(view: UIView) {
|
||||
|
||||
let xTranslationFactor: CGFloat = 0
|
||||
let yTranslationFactor: CGFloat = 0.18
|
||||
@@ -66,7 +66,7 @@ extension SPShadow {
|
||||
yTranslation = view.frame.height + maxBottomSpace - shadowHeight
|
||||
}
|
||||
|
||||
var blurRadius = view.frame.minSideSize * blurRadiusFactor
|
||||
var blurRadius = view.frame.minSide * blurRadiusFactor
|
||||
blurRadius.setIfMore(when: 10)
|
||||
blurRadius.setIfFewer(when: 7)
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public struct SPShare {
|
||||
struct SPShare {
|
||||
|
||||
public struct Native {
|
||||
struct Native {
|
||||
|
||||
static func share(text: String? = nil, fileNames: [String] = [], images: [UIImage] = [], complection: ((_ isSharing: Bool)->())? = nil, sourceView: UIView, on viewController: UIViewController) {
|
||||
|
||||
@@ -24,11 +24,7 @@ import UIKit
|
||||
class SPInstagram {
|
||||
|
||||
static var isSetApp: Bool {
|
||||
if UIApplication.shared.canOpenURL(URL(string: "instagram://user?username=test")!) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return UIApplication.shared.canOpenURL(URL(string: "instagram://user?username=test")!)
|
||||
}
|
||||
|
||||
static func openPost(id: String) {
|
||||
@@ -38,7 +34,7 @@ class SPInstagram {
|
||||
if UIApplication.shared.canOpenURL(instagramUrl!) {
|
||||
UIApplication.shared.open(instagramUrl!, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
|
||||
} else {
|
||||
SPOpener.Link.redirectToBrowserAndOpen(link: safariURL)
|
||||
SPApp.open(link: safariURL.absoluteString)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,14 +45,13 @@ class SPInstagram {
|
||||
if UIApplication.shared.canOpenURL(instagramUrl!) {
|
||||
UIApplication.shared.open(instagramUrl!, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
|
||||
} else {
|
||||
SPOpener.Link.redirectToBrowserAndOpen(link: safariURL)
|
||||
SPApp.open(link: safariURL.absoluteString)
|
||||
}
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
// Helper function inserted by Swift 4.2 migrator.
|
||||
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
|
||||
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
|
||||
}
|
||||
@@ -24,11 +24,7 @@ import UIKit
|
||||
class SPTelegram {
|
||||
|
||||
static var isSetApp: Bool {
|
||||
if UIApplication.shared.canOpenURL(URL(string: "tg://msg?text=test")!) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return UIApplication.shared.canOpenURL(URL(string: "tg://msg?text=test")!)
|
||||
}
|
||||
|
||||
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
|
||||
@@ -47,7 +43,12 @@ class SPTelegram {
|
||||
|
||||
static func joinChannel(id: String) {
|
||||
let url = "https://t.me/joinchat/\(id)"
|
||||
SPOpener.Link.redirectToBrowserAndOpen(link: url)
|
||||
SPApp.open(link: url)
|
||||
}
|
||||
|
||||
static func joinChat(id: String) {
|
||||
let url = "https://t.me/joinchat/\(id)"
|
||||
SPApp.open(link: url)
|
||||
}
|
||||
|
||||
static func openBot(username: String) {
|
||||
@@ -56,13 +57,17 @@ class SPTelegram {
|
||||
username.removeFirst()
|
||||
}
|
||||
let url = "https://telegram.me/\(username)"
|
||||
SPOpener.Link.redirectToBrowserAndOpen(link: url)
|
||||
SPApp.open(link: url)
|
||||
}
|
||||
|
||||
static func openDialog(username: String) {
|
||||
let url = "https://t.me/\(username)"
|
||||
SPApp.open(link: url)
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
|
||||
// Helper function inserted by Swift 4.2 migrator.
|
||||
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
|
||||
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
|
||||
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
|
||||
}
|
||||
@@ -24,11 +24,7 @@ import UIKit
|
||||
class SPTwitter {
|
||||
|
||||
static var isSetApp: Bool {
|
||||
if UIApplication.shared.canOpenURL(URL(string: "twitter://post?message=test")!) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return UIApplication.shared.canOpenURL(URL(string: "twitter://post?message=test")!)
|
||||
}
|
||||
|
||||
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
|
||||
@@ -48,7 +44,6 @@ class SPTwitter {
|
||||
private init() {}
|
||||
}
|
||||
|
||||
// Helper function inserted by Swift 4.2 migrator.
|
||||
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
|
||||
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
|
||||
}
|
||||