Compare commits

..

13 Commits

Author SHA1 Message Date
Ivan Vorobei 34f267d43c Update SPStorkController.podspec 2018-11-29 12:55:29 +03:00
Ivan Vorobei 425f8ec1ca Fix layout for SPStorkController
- Fix layout for SPStorkController
- Add extenshion for `CGRect` static var `displayFrame`. It is rect for screen size
- Set clear background color for `SPStorkIndicatorView`
2018-11-29 12:49:56 +03:00
Ivan Vorobei 25ea7eab4c Add more interactive to SPStorkController 2018-11-29 11:10:18 +03:00
Ivan Vorobei 16f060820f Update README.md 2018-11-28 20:11:41 +03:00
Ivan Vorobei 4c8faf7695 Update README.md 2018-11-28 17:49:37 +03:00
Ivan Vorobei 5b843582d4 Update README.md 2018-11-28 17:30:29 +03:00
Ivan Vorobei 8c58dbd2a3 Add example 2018-11-28 17:16:44 +03:00
Ivan Vorobei 366f6dffd1 Update Pospec 2018-11-28 17:16:12 +03:00
Ivan Vorobei 0141560c98 Create Example 2018-11-28 17:14:05 +03:00
Ivan Vorobei 900d4cbbc9 Add example 2018-11-28 17:13:23 +03:00
Ivan Vorobei dd8137b640 Update README.md 2018-11-28 17:07:21 +03:00
Ivan Vorobei 0118f80729 Update readme 2018-11-28 17:06:20 +03:00
Ivan Vorobei d0412a4285 Test submodule 2018-11-28 16:32:54 +03:00
152 changed files with 24147 additions and 5 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:stork-controller.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>stork-controller.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>
@@ -0,0 +1,37 @@
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
@@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,24 @@
<?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,65 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPAnimation {
static func animate(_ duration: TimeInterval,
animations: (() -> Void)!,
delay: TimeInterval = 0,
options: UIView.AnimationOptions = [],
withComplection completion: (() -> Void)! = {}) {
UIView.animate(
withDuration: duration,
delay: delay,
options: options,
animations: {
animations()
}, completion: { finished in
completion()
})
}
static func animateWithRepeatition(_ duration: TimeInterval,
animations: (() -> Void)!,
delay: TimeInterval = 0,
options: UIView.AnimationOptions = [],
withComplection completion: (() -> Void)! = {}) {
var optionsWithRepeatition = options
optionsWithRepeatition.insert([.autoreverse, .repeat])
self.animate(
duration,
animations: {
animations()
},
delay: delay,
options: optionsWithRepeatition,
withComplection: {
completion()
})
}
}
@@ -0,0 +1,103 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPAnimationAlpha {
fileprivate static let durationListAnimation: TimeInterval = 0.45
fileprivate static let coefLenthForTransition: CGFloat = 2.8
fileprivate static let delayPerItem: TimeInterval = 0.09
static func hideList(_ duration: TimeInterval = durationListAnimation,
views: [UIView],
delayPerItem: TimeInterval = delayPerItem,
withComplection completion: (() -> Void)! = {}) {
var del: Double = 0
for view in views {
delay(del, closure: {
if (view == views.last) {
SPAnimation.animate(duration, animations: {
view.alpha = 0
}, withComplection: {
completion()
})
} else {
SPAnimation.animate(duration, animations: {
view.alpha = 0
})
}
})
del += delayPerItem
}
}
static func hideReverseList(_ duration: TimeInterval = durationListAnimation,
views: [UIView],
delayPerItem: TimeInterval = delayPerItem,
withComplection completion: (() -> Void)! = {}) {
var del: Double = 0
let reversedViews = views.reversed()
for view in reversedViews {
delay(del, closure: {
if (view == views.last) {
SPAnimation.animate(duration, animations: {
view.alpha = 0
}, withComplection: {
completion()
})
} else {
SPAnimation.animate(duration, animations: {
view.alpha = 0
})
}
})
del += delayPerItem
}
}
static func showList(_ duration: TimeInterval = durationListAnimation,
views: [UIView],
delayPerItem: TimeInterval = delayPerItem,
withComplection completion: (() -> Void)! = {}) {
var del: Double = 0
for view in views {
delay(del, closure: {
if (view == views.last) {
SPAnimation.animate(duration, animations: {
view.alpha = 1
}, withComplection: {
completion()
})
} else {
SPAnimation.animate(duration, animations: {
view.alpha = 1
})
}
})
del += delayPerItem
}
}
}
@@ -0,0 +1,73 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPAnimationSpring {
fileprivate static let spring: CGFloat = 1
fileprivate static let velocity: CGFloat = 1
static func animate(_ duration: TimeInterval,
animations: (() -> Void)!,
delay: TimeInterval = 0,
spring: CGFloat = spring,
velocity: CGFloat = velocity,
options: UIView.AnimationOptions = [],
withComplection completion: (() -> Void)! = {}) {
UIView.animate(
withDuration: duration,
delay: delay,
usingSpringWithDamping: spring,
initialSpringVelocity: velocity,
options: options,
animations: {
animations()
}, completion: { finished in
completion()
})
}
static func animateWithRepeatition(_ duration: TimeInterval,
animations: (() -> Void)!,
delay: TimeInterval = 0,
spring: CGFloat = spring,
velocity: CGFloat = velocity,
options: UIView.AnimationOptions = [],
withComplection completion: (() -> Void)! = {}) {
var optionsWithRepeatition = options
optionsWithRepeatition.insert([.autoreverse, .repeat])
UIView.animate(
withDuration: duration,
delay: delay,
usingSpringWithDamping: spring,
initialSpringVelocity: velocity,
options: optionsWithRepeatition,
animations: {
animations()
}, completion: { finished in
completion()
})
}
}
@@ -0,0 +1,119 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPAnimationUpward {
fileprivate static let durationListAnimation: TimeInterval = 0.45
fileprivate static let coefLenthForTransition: CGFloat = 2.8
fileprivate static let delayPerItem: TimeInterval = 0.09
static func hide(_ duration: TimeInterval,
view: UIView,
delay: TimeInterval = 0,
withComplection completion: (() -> Void)! = {}) {
var options: UIView.AnimationOptions = []
options.insert(.curveEaseIn)
SPAnimationSpring.animate(
duration, animations: {
view.alpha = 0
view.frame.origin.y = view.frame.origin.y + (-UIScreen.main.bounds.height / coefLenthForTransition)
},
delay: delay,
options: options,
withComplection: {
completion()
})
}
static func hideList(_ duration: TimeInterval = durationListAnimation,
views: [UIView],
delayPerItem: TimeInterval = delayPerItem,
withComplection completion: (() -> Void)! = {}) {
var del: Double = 0
for view in views {
delay(del, closure: {
if (view == views.last) {
SPAnimationUpward.hide(duration, view: view, withComplection: {
completion()
})
} else {
SPAnimationUpward.hide(duration, view: view)
}
})
del += delayPerItem
}
}
static func show(_ duration: TimeInterval,
view: UIView,
delay: TimeInterval = 0,
withComplection completion: (() -> Void)! = {}) {
view.alpha = 0
view.isHidden = false
view.transform = CGAffineTransform(translationX: 0, y: UIScreen.main.bounds.height / coefLenthForTransition)
var options: UIView.AnimationOptions = []
options.insert(.curveEaseOut)
SPAnimationSpring.animate(
duration, animations: {
view.alpha = 1
view.transform = CGAffineTransform.identity
},
delay: delay,
options: options,
withComplection: {
completion()
})
}
static func showList(_ duration: TimeInterval = durationListAnimation,
views: [UIView],
delayPerItem: TimeInterval = delayPerItem,
options: UIView.AnimationOptions = [],
withComplection completion: (() -> Void)! = {}) {
var del: Double = 0
for view in views {
delay(del, closure: {
if (view == views.last) {
SPAnimationUpward.show(duration, view: view, withComplection: {
completion()
})
} else {
SPAnimationUpward.show(duration, view: view)
}
})
del += delayPerItem
}
}
}
@@ -0,0 +1,134 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
import StoreKit
struct SPAppStore {
static func link(appID: String) -> String {
return "https://itunes.apple.com/by/app/id" + appID
}
static func open(appID: String) {
if let url = URL(string: "itms-apps://itunes.apple.com/app/id\(appID)"),
UIApplication.shared.canOpenURL(url) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
}
}
static func requestReview(appID: String, force: Bool) {
if force {
if let url = URL(string: "itms-apps://itunes.apple.com/us/app/apple-store/id\(appID)?mt=8&action=write-review"),
UIApplication.shared.canOpenURL(url) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
}
} else {
if #available(iOS 10.3, *) {
SKStoreReviewController.requestReview()
}
}
}
static func isUpdateAvailable(completion: @escaping (Bool)->()) {
guard let info = Bundle.main.infoDictionary,
let currentVersion = info["CFBundleShortVersionString"] as? String,
let identifier = info["CFBundleIdentifier"] as? String,
let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)") else {
completion(false)
return
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
if let error = error { throw error }
guard let data = data else {
completion(false)
return
}
let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any]
guard let result = (json?["results"] as? [Any])?.first as? [String: Any], let version = result["version"] as? String else {
completion(false)
return
}
let compareResult = currentVersion.compare(version, options: .numeric)
DispatchQueue.main.async {
completion(compareResult == .orderedAscending)
}
} catch {
completion(false)
}
}
task.resume()
}
private init() {}
}
extension NSRange {
func range(for str: String) -> Range<String.Index>? {
guard location != NSNotFound else { return nil }
guard let fromUTFIndex = str.utf16.index(str.utf16.startIndex, offsetBy: location, limitedBy: str.utf16.endIndex) else { return nil }
guard let toUTFIndex = str.utf16.index(fromUTFIndex, offsetBy: length, limitedBy: str.utf16.endIndex) else { return nil }
guard let fromIndex = String.Index(fromUTFIndex, within: str) else { return nil }
guard let toIndex = String.Index(toUTFIndex, within: str) else { return nil }
return fromIndex ..< toIndex
}
}
extension String {
func ranges(of string: String, options: CompareOptions = .literal) -> [Range<Index>] {
var result: [Range<Index>] = []
var start = startIndex
while let range = range(of: string, options: options, range: start..<endIndex) {
result.append(range)
start = range.lowerBound < range.upperBound ? range.upperBound : index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
}
return result
}
func slices(from: String, to: String) -> [Substring] {
let pattern = "(?<=" + from + ").*?(?=" + to + ")"
return ranges(of: pattern, options: .regularExpression)
.map{ self[$0] }
}
}
// 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,35 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
struct SPApp {
static var udid: String? {
return UIDevice.current.identifierForVendor?.uuidString
}
static var displayName: String? {
return Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
}
private init() {}
}
@@ -0,0 +1,42 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
import AVFoundation
public struct SPAudio {
static func notStopBackgroundMusic() {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category(rawValue: convertFromAVAudioSessionCategory(AVAudioSession.Category.ambient)), mode: AVAudioSession.Mode.default)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
}
}
private init() {}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertFromAVAudioSessionCategory(_ input: AVAudioSession.Category) -> String {
return input.rawValue
}
@@ -0,0 +1,57 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
import AVFoundation
public class SPAudioPlayer: NSObject, AVAudioPlayerDelegate {
fileprivate var player: AVAudioPlayer = AVAudioPlayer()
fileprivate var endPlayingComplection: (()->())? = nil
func play(fileName: String, complection: (()->())? = nil) {
self.endPlayingComplection?()
self.player = AVAudioPlayer()
let url = Bundle.main.url(forResource: fileName, withExtension: nil)
if url == nil {
self.endPlayingComplection?()
return
}
do {
self.player = try AVAudioPlayer(contentsOf: url!)
player.volume = 1
player.delegate = self
player.prepareToPlay()
player.play()
self.endPlayingComplection = complection
} catch let error as NSError {
print(error.description)
}
}
func stop() {
player.stop()
}
public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
self.endPlayingComplection?()
}
}
@@ -0,0 +1,40 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public struct SPBadge {
static func reset() {
UIApplication.shared.applicationIconBadgeNumber = 0
}
static var number: Int {
get {
return UIApplication.shared.applicationIconBadgeNumber
}
set {
UIApplication.shared.applicationIconBadgeNumber = newValue
}
}
private init() {}
}
@@ -0,0 +1,37 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
struct SPBufer {
static var text: String? {
get {
return UIPasteboard.general.string
}
set {
UIPasteboard.general.string = newValue
}
}
private init() {}
}
@@ -0,0 +1,161 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension SPCodeDraw {
public class AudioIconPack : NSObject {
@objc dynamic public class func drawPlay(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 200, height: 200), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 200, y: resizedFrame.height / 200)
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 177.85, y: 86.98))
bezierPath.addLine(to: CGPoint(x: 54.26, y: 9.11))
bezierPath.addCurve(to: CGPoint(x: 42.64, y: 7.3), controlPoint1: CGPoint(x: 50.61, y: 6.94), controlPoint2: CGPoint(x: 46.07, y: 7.3))
bezierPath.addCurve(to: CGPoint(x: 29, y: 20.56), controlPoint1: CGPoint(x: 28.94, y: 7.3), controlPoint2: CGPoint(x: 29, y: 17.88))
bezierPath.addLine(to: CGPoint(x: 29, y: 179.72))
bezierPath.addCurve(to: CGPoint(x: 42.64, y: 192.98), controlPoint1: CGPoint(x: 29, y: 181.98), controlPoint2: CGPoint(x: 28.94, y: 192.98))
bezierPath.addCurve(to: CGPoint(x: 54.26, y: 191.16), controlPoint1: CGPoint(x: 46.07, y: 192.98), controlPoint2: CGPoint(x: 50.61, y: 193.34))
bezierPath.addLine(to: CGPoint(x: 177.85, y: 113.3))
bezierPath.addCurve(to: CGPoint(x: 186.24, y: 100.14), controlPoint1: CGPoint(x: 187.99, y: 107.26), controlPoint2: CGPoint(x: 186.24, y: 100.14))
bezierPath.addCurve(to: CGPoint(x: 177.85, y: 86.98), controlPoint1: CGPoint(x: 186.24, y: 100.14), controlPoint2: CGPoint(x: 187.99, y: 93.02))
bezierPath.close()
bezierPath.usesEvenOddFillRule = true
fillColor.setFill()
bezierPath.fill()
context.restoreGState()
}
@objc dynamic public class func drawPause(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 200, height: 200), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 200, y: resizedFrame.height / 200)
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 70.11, y: 6.86))
bezierPath.addLine(to: CGPoint(x: 34.32, y: 6.86))
bezierPath.addCurve(to: CGPoint(x: 20, y: 21.18), controlPoint1: CGPoint(x: 26.41, y: 6.86), controlPoint2: CGPoint(x: 20, y: 13.27))
bezierPath.addLine(to: CGPoint(x: 20, y: 178.68))
bezierPath.addCurve(to: CGPoint(x: 34.32, y: 193), controlPoint1: CGPoint(x: 20, y: 186.59), controlPoint2: CGPoint(x: 26.41, y: 193))
bezierPath.addLine(to: CGPoint(x: 70.11, y: 193))
bezierPath.addCurve(to: CGPoint(x: 84.43, y: 178.68), controlPoint1: CGPoint(x: 78.02, y: 193), controlPoint2: CGPoint(x: 84.43, y: 186.59))
bezierPath.addLine(to: CGPoint(x: 84.43, y: 21.18))
bezierPath.addCurve(to: CGPoint(x: 70.11, y: 6.86), controlPoint1: CGPoint(x: 84.43, y: 13.27), controlPoint2: CGPoint(x: 78.02, y: 6.86))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 163.19, y: 6.86))
bezierPath.addLine(to: CGPoint(x: 127.39, y: 6.86))
bezierPath.addCurve(to: CGPoint(x: 113.07, y: 21.18), controlPoint1: CGPoint(x: 119.48, y: 6.86), controlPoint2: CGPoint(x: 113.07, y: 13.27))
bezierPath.addLine(to: CGPoint(x: 113.07, y: 178.68))
bezierPath.addCurve(to: CGPoint(x: 127.39, y: 193), controlPoint1: CGPoint(x: 113.07, y: 186.59), controlPoint2: CGPoint(x: 119.48, y: 193))
bezierPath.addLine(to: CGPoint(x: 163.19, y: 193))
bezierPath.addCurve(to: CGPoint(x: 177.5, y: 178.68), controlPoint1: CGPoint(x: 171.09, y: 193), controlPoint2: CGPoint(x: 177.5, y: 186.59))
bezierPath.addLine(to: CGPoint(x: 177.5, y: 21.18))
bezierPath.addCurve(to: CGPoint(x: 163.19, y: 6.86), controlPoint1: CGPoint(x: 177.5, y: 13.27), controlPoint2: CGPoint(x: 171.09, y: 6.86))
bezierPath.close()
bezierPath.usesEvenOddFillRule = true
fillColor.setFill()
bezierPath.fill()
context.restoreGState()
}
@objc dynamic public class func drawStop(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 200, height: 200), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 200, y: resizedFrame.height / 200)
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 177.82, y: 7.86))
bezierPath.addLine(to: CGPoint(x: 20.32, y: 7.86))
bezierPath.addCurve(to: CGPoint(x: 6, y: 22.18), controlPoint1: CGPoint(x: 12.41, y: 7.86), controlPoint2: CGPoint(x: 6, y: 14.27))
bezierPath.addLine(to: CGPoint(x: 6, y: 179.68))
bezierPath.addCurve(to: CGPoint(x: 20.32, y: 194), controlPoint1: CGPoint(x: 6, y: 187.59), controlPoint2: CGPoint(x: 12.41, y: 194))
bezierPath.addLine(to: CGPoint(x: 177.82, y: 194))
bezierPath.addCurve(to: CGPoint(x: 192.15, y: 179.68), controlPoint1: CGPoint(x: 185.73, y: 194), controlPoint2: CGPoint(x: 192.15, y: 187.59))
bezierPath.addLine(to: CGPoint(x: 192.15, y: 22.18))
bezierPath.addCurve(to: CGPoint(x: 177.82, y: 7.86), controlPoint1: CGPoint(x: 192.15, y: 14.27), controlPoint2: CGPoint(x: 185.73, y: 7.86))
bezierPath.close()
bezierPath.usesEvenOddFillRule = true
fillColor.setFill()
bezierPath.fill()
context.restoreGState()
}
@objc(StyleKitNameResizingBehavior)
public enum ResizingBehavior: Int {
case aspectFit /// The content is proportionally resized to fit into the target rectangle.
case aspectFill /// The content is proportionally resized to completely fill the target rectangle.
case stretch /// The content is stretched to match the entire target rectangle.
case center /// The content is centered in the target rectangle, but it is NOT resized.
public func apply(rect: CGRect, target: CGRect) -> CGRect {
if rect == target || target == CGRect.zero {
return rect
}
var scales = CGSize.zero
scales.width = abs(target.width / rect.width)
scales.height = abs(target.height / rect.height)
switch self {
case .aspectFit:
scales.width = min(scales.width, scales.height)
scales.height = scales.width
case .aspectFill:
scales.width = max(scales.width, scales.height)
scales.height = scales.width
case .stretch:
break
case .center:
scales.width = 1
scales.height = 1
}
var result = rect.standardized
result.size.width *= scales.width
result.size.height *= scales.height
result.origin.x = target.minX + (target.width - result.width) / 2
result.origin.y = target.minY + (target.height - result.height) / 2
return result
}
}
private override init() {}
}
}
@@ -0,0 +1,24 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
struct SPCodeDraw { private init(){} }
@@ -0,0 +1,486 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension SPCodeDraw {
public class SocialIconPack : NSObject {
@objc dynamic public class func drawInstagram(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 40, height: 40), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 40, y: resizedFrame.height / 40)
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 19.9, y: 0.99))
bezierPath.addCurve(to: CGPoint(x: 12.12, y: 1.1), controlPoint1: CGPoint(x: 14.77, y: 0.99), controlPoint2: CGPoint(x: 14.13, y: 1.01))
bezierPath.addCurve(to: CGPoint(x: 7.54, y: 1.98), controlPoint1: CGPoint(x: 10.11, y: 1.19), controlPoint2: CGPoint(x: 8.74, y: 1.51))
bezierPath.addCurve(to: CGPoint(x: 4.19, y: 4.15), controlPoint1: CGPoint(x: 6.29, y: 2.46), controlPoint2: CGPoint(x: 5.24, y: 3.1))
bezierPath.addCurve(to: CGPoint(x: 2.02, y: 7.5), controlPoint1: CGPoint(x: 3.14, y: 5.2), controlPoint2: CGPoint(x: 2.5, y: 6.26))
bezierPath.addCurve(to: CGPoint(x: 1.14, y: 12.08), controlPoint1: CGPoint(x: 1.55, y: 8.7), controlPoint2: CGPoint(x: 1.23, y: 10.07))
bezierPath.addCurve(to: CGPoint(x: 1.03, y: 19.86), controlPoint1: CGPoint(x: 1.05, y: 14.09), controlPoint2: CGPoint(x: 1.03, y: 14.73))
bezierPath.addCurve(to: CGPoint(x: 1.14, y: 27.64), controlPoint1: CGPoint(x: 1.03, y: 24.98), controlPoint2: CGPoint(x: 1.05, y: 25.63))
bezierPath.addCurve(to: CGPoint(x: 2.02, y: 32.22), controlPoint1: CGPoint(x: 1.23, y: 29.65), controlPoint2: CGPoint(x: 1.55, y: 31.02))
bezierPath.addCurve(to: CGPoint(x: 4.19, y: 35.56), controlPoint1: CGPoint(x: 2.5, y: 33.46), controlPoint2: CGPoint(x: 3.14, y: 34.51))
bezierPath.addCurve(to: CGPoint(x: 7.54, y: 37.74), controlPoint1: CGPoint(x: 5.24, y: 36.61), controlPoint2: CGPoint(x: 6.29, y: 37.26))
bezierPath.addCurve(to: CGPoint(x: 12.12, y: 38.62), controlPoint1: CGPoint(x: 8.74, y: 38.21), controlPoint2: CGPoint(x: 10.11, y: 38.52))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 38.73), controlPoint1: CGPoint(x: 14.13, y: 38.71), controlPoint2: CGPoint(x: 14.77, y: 38.73))
bezierPath.addCurve(to: CGPoint(x: 27.68, y: 38.62), controlPoint1: CGPoint(x: 25.02, y: 38.73), controlPoint2: CGPoint(x: 25.67, y: 38.71))
bezierPath.addCurve(to: CGPoint(x: 32.26, y: 37.74), controlPoint1: CGPoint(x: 29.69, y: 38.52), controlPoint2: CGPoint(x: 31.06, y: 38.21))
bezierPath.addCurve(to: CGPoint(x: 35.6, y: 35.56), controlPoint1: CGPoint(x: 33.5, y: 37.26), controlPoint2: CGPoint(x: 34.55, y: 36.61))
bezierPath.addCurve(to: CGPoint(x: 37.78, y: 32.22), controlPoint1: CGPoint(x: 36.65, y: 34.51), controlPoint2: CGPoint(x: 37.3, y: 33.46))
bezierPath.addCurve(to: CGPoint(x: 38.66, y: 27.64), controlPoint1: CGPoint(x: 38.25, y: 31.02), controlPoint2: CGPoint(x: 38.56, y: 29.65))
bezierPath.addCurve(to: CGPoint(x: 38.77, y: 19.86), controlPoint1: CGPoint(x: 38.75, y: 25.63), controlPoint2: CGPoint(x: 38.77, y: 24.98))
bezierPath.addCurve(to: CGPoint(x: 38.66, y: 12.08), controlPoint1: CGPoint(x: 38.77, y: 14.73), controlPoint2: CGPoint(x: 38.75, y: 14.09))
bezierPath.addCurve(to: CGPoint(x: 37.78, y: 7.5), controlPoint1: CGPoint(x: 38.56, y: 10.07), controlPoint2: CGPoint(x: 38.25, y: 8.7))
bezierPath.addCurve(to: CGPoint(x: 35.6, y: 4.15), controlPoint1: CGPoint(x: 37.3, y: 6.26), controlPoint2: CGPoint(x: 36.65, y: 5.2))
bezierPath.addCurve(to: CGPoint(x: 32.26, y: 1.98), controlPoint1: CGPoint(x: 34.55, y: 3.1), controlPoint2: CGPoint(x: 33.5, y: 2.46))
bezierPath.addCurve(to: CGPoint(x: 27.68, y: 1.1), controlPoint1: CGPoint(x: 31.06, y: 1.51), controlPoint2: CGPoint(x: 29.69, y: 1.19))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 0.99), controlPoint1: CGPoint(x: 25.67, y: 1.01), controlPoint2: CGPoint(x: 25.02, y: 0.99))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 19.9, y: 4.39))
bezierPath.addCurve(to: CGPoint(x: 27.52, y: 4.5), controlPoint1: CGPoint(x: 24.94, y: 4.39), controlPoint2: CGPoint(x: 25.53, y: 4.41))
bezierPath.addCurve(to: CGPoint(x: 31.03, y: 5.15), controlPoint1: CGPoint(x: 29.36, y: 4.58), controlPoint2: CGPoint(x: 30.36, y: 4.89))
bezierPath.addCurve(to: CGPoint(x: 33.2, y: 6.56), controlPoint1: CGPoint(x: 31.91, y: 5.49), controlPoint2: CGPoint(x: 32.54, y: 5.9))
bezierPath.addCurve(to: CGPoint(x: 34.61, y: 8.73), controlPoint1: CGPoint(x: 33.86, y: 7.22), controlPoint2: CGPoint(x: 34.27, y: 7.85))
bezierPath.addCurve(to: CGPoint(x: 35.26, y: 12.23), controlPoint1: CGPoint(x: 34.87, y: 9.39), controlPoint2: CGPoint(x: 35.18, y: 10.39))
bezierPath.addCurve(to: CGPoint(x: 35.37, y: 19.86), controlPoint1: CGPoint(x: 35.35, y: 14.22), controlPoint2: CGPoint(x: 35.37, y: 14.82))
bezierPath.addCurve(to: CGPoint(x: 35.26, y: 27.48), controlPoint1: CGPoint(x: 35.37, y: 24.9), controlPoint2: CGPoint(x: 35.35, y: 25.49))
bezierPath.addCurve(to: CGPoint(x: 34.61, y: 30.99), controlPoint1: CGPoint(x: 35.18, y: 29.32), controlPoint2: CGPoint(x: 34.87, y: 30.32))
bezierPath.addCurve(to: CGPoint(x: 33.2, y: 33.16), controlPoint1: CGPoint(x: 34.27, y: 31.87), controlPoint2: CGPoint(x: 33.86, y: 32.5))
bezierPath.addCurve(to: CGPoint(x: 31.03, y: 34.57), controlPoint1: CGPoint(x: 32.54, y: 33.82), controlPoint2: CGPoint(x: 31.91, y: 34.23))
bezierPath.addCurve(to: CGPoint(x: 27.52, y: 35.22), controlPoint1: CGPoint(x: 30.36, y: 34.83), controlPoint2: CGPoint(x: 29.36, y: 35.14))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 35.33), controlPoint1: CGPoint(x: 25.53, y: 35.31), controlPoint2: CGPoint(x: 24.94, y: 35.33))
bezierPath.addCurve(to: CGPoint(x: 12.27, y: 35.22), controlPoint1: CGPoint(x: 14.86, y: 35.33), controlPoint2: CGPoint(x: 14.26, y: 35.31))
bezierPath.addCurve(to: CGPoint(x: 8.77, y: 34.57), controlPoint1: CGPoint(x: 10.43, y: 35.14), controlPoint2: CGPoint(x: 9.43, y: 34.83))
bezierPath.addCurve(to: CGPoint(x: 6.6, y: 33.16), controlPoint1: CGPoint(x: 7.89, y: 34.23), controlPoint2: CGPoint(x: 7.26, y: 33.82))
bezierPath.addCurve(to: CGPoint(x: 5.19, y: 30.99), controlPoint1: CGPoint(x: 5.94, y: 32.5), controlPoint2: CGPoint(x: 5.53, y: 31.87))
bezierPath.addCurve(to: CGPoint(x: 4.54, y: 27.48), controlPoint1: CGPoint(x: 4.93, y: 30.32), controlPoint2: CGPoint(x: 4.62, y: 29.32))
bezierPath.addCurve(to: CGPoint(x: 4.43, y: 19.86), controlPoint1: CGPoint(x: 4.45, y: 25.49), controlPoint2: CGPoint(x: 4.43, y: 24.9))
bezierPath.addCurve(to: CGPoint(x: 4.54, y: 12.23), controlPoint1: CGPoint(x: 4.43, y: 14.82), controlPoint2: CGPoint(x: 4.45, y: 14.22))
bezierPath.addCurve(to: CGPoint(x: 5.19, y: 8.73), controlPoint1: CGPoint(x: 4.62, y: 10.39), controlPoint2: CGPoint(x: 4.93, y: 9.39))
bezierPath.addCurve(to: CGPoint(x: 6.6, y: 6.56), controlPoint1: CGPoint(x: 5.53, y: 7.85), controlPoint2: CGPoint(x: 5.94, y: 7.22))
bezierPath.addCurve(to: CGPoint(x: 8.77, y: 5.15), controlPoint1: CGPoint(x: 7.26, y: 5.9), controlPoint2: CGPoint(x: 7.89, y: 5.49))
bezierPath.addCurve(to: CGPoint(x: 12.27, y: 4.5), controlPoint1: CGPoint(x: 9.43, y: 4.89), controlPoint2: CGPoint(x: 10.43, y: 4.58))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 4.39), controlPoint1: CGPoint(x: 14.26, y: 4.41), controlPoint2: CGPoint(x: 14.86, y: 4.39))
bezierPath.addLine(to: CGPoint(x: 19.9, y: 4.39))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 19.9, y: 26.15))
bezierPath.addCurve(to: CGPoint(x: 13.61, y: 19.86), controlPoint1: CGPoint(x: 16.42, y: 26.15), controlPoint2: CGPoint(x: 13.61, y: 23.33))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 13.57), controlPoint1: CGPoint(x: 13.61, y: 16.38), controlPoint2: CGPoint(x: 16.42, y: 13.57))
bezierPath.addCurve(to: CGPoint(x: 26.19, y: 19.86), controlPoint1: CGPoint(x: 23.37, y: 13.57), controlPoint2: CGPoint(x: 26.19, y: 16.38))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 26.15), controlPoint1: CGPoint(x: 26.19, y: 23.33), controlPoint2: CGPoint(x: 23.37, y: 26.15))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 19.9, y: 10.17))
bezierPath.addCurve(to: CGPoint(x: 10.21, y: 19.86), controlPoint1: CGPoint(x: 14.55, y: 10.17), controlPoint2: CGPoint(x: 10.21, y: 14.51))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 29.55), controlPoint1: CGPoint(x: 10.21, y: 25.21), controlPoint2: CGPoint(x: 14.55, y: 29.55))
bezierPath.addCurve(to: CGPoint(x: 29.59, y: 19.86), controlPoint1: CGPoint(x: 25.25, y: 29.55), controlPoint2: CGPoint(x: 29.59, y: 25.21))
bezierPath.addCurve(to: CGPoint(x: 19.9, y: 10.17), controlPoint1: CGPoint(x: 29.59, y: 14.51), controlPoint2: CGPoint(x: 25.25, y: 10.17))
bezierPath.addLine(to: CGPoint(x: 19.9, y: 10.17))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 32.43, y: 9.57))
bezierPath.addCurve(to: CGPoint(x: 30.23, y: 11.77), controlPoint1: CGPoint(x: 32.43, y: 10.79), controlPoint2: CGPoint(x: 31.44, y: 11.77))
bezierPath.addCurve(to: CGPoint(x: 28.03, y: 9.57), controlPoint1: CGPoint(x: 29.01, y: 11.77), controlPoint2: CGPoint(x: 28.03, y: 10.79))
bezierPath.addCurve(to: CGPoint(x: 30.23, y: 7.38), controlPoint1: CGPoint(x: 28.03, y: 8.36), controlPoint2: CGPoint(x: 29.01, y: 7.38))
bezierPath.addCurve(to: CGPoint(x: 32.43, y: 9.57), controlPoint1: CGPoint(x: 31.44, y: 7.38), controlPoint2: CGPoint(x: 32.43, y: 8.36))
bezierPath.close()
fillColor.setFill()
bezierPath.fill()
context.restoreGState()
}
@objc dynamic public class func drawVK(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()!
//// Resize to Target Frame
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 40, height: 40), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 40, y: resizedFrame.height / 40)
//// Bezier Drawing
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 32.57, y: 19.79))
bezierPath.addCurve(to: CGPoint(x: 38.07, y: 10.51), controlPoint1: CGPoint(x: 32.57, y: 19.79), controlPoint2: CGPoint(x: 37.57, y: 12.8))
bezierPath.addCurve(to: CGPoint(x: 37.01, y: 9.23), controlPoint1: CGPoint(x: 38.24, y: 9.69), controlPoint2: CGPoint(x: 37.87, y: 9.23))
bezierPath.addCurve(to: CGPoint(x: 32.67, y: 9.23), controlPoint1: CGPoint(x: 37.01, y: 9.23), controlPoint2: CGPoint(x: 34.13, y: 9.23))
bezierPath.addCurve(to: CGPoint(x: 31.01, y: 10.28), controlPoint1: CGPoint(x: 31.67, y: 9.23), controlPoint2: CGPoint(x: 31.31, y: 9.66))
bezierPath.addCurve(to: CGPoint(x: 25.8, y: 18.34), controlPoint1: CGPoint(x: 31.01, y: 10.28), controlPoint2: CGPoint(x: 28.66, y: 15.23))
bezierPath.addCurve(to: CGPoint(x: 23.91, y: 19.66), controlPoint1: CGPoint(x: 24.89, y: 19.35), controlPoint2: CGPoint(x: 24.42, y: 19.66))
bezierPath.addCurve(to: CGPoint(x: 23.32, y: 18.41), controlPoint1: CGPoint(x: 23.5, y: 19.66), controlPoint2: CGPoint(x: 23.32, y: 19.32))
bezierPath.addLine(to: CGPoint(x: 23.32, y: 10.44))
bezierPath.addCurve(to: CGPoint(x: 22.25, y: 9), controlPoint1: CGPoint(x: 23.32, y: 9.33), controlPoint2: CGPoint(x: 23.18, y: 9))
bezierPath.addLine(to: CGPoint(x: 15.29, y: 9))
bezierPath.addCurve(to: CGPoint(x: 14.43, y: 9.72), controlPoint1: CGPoint(x: 14.76, y: 9), controlPoint2: CGPoint(x: 14.43, y: 9.31))
bezierPath.addCurve(to: CGPoint(x: 16.05, y: 13.85), controlPoint1: CGPoint(x: 14.43, y: 10.77), controlPoint2: CGPoint(x: 16.05, y: 11.01))
bezierPath.addLine(to: CGPoint(x: 16.05, y: 19.72))
bezierPath.addCurve(to: CGPoint(x: 15.42, y: 21.36), controlPoint1: CGPoint(x: 16.05, y: 20.9), controlPoint2: CGPoint(x: 15.99, y: 21.36))
bezierPath.addCurve(to: CGPoint(x: 8.39, y: 10.51), controlPoint1: CGPoint(x: 13.93, y: 21.36), controlPoint2: CGPoint(x: 10.38, y: 16.28))
bezierPath.addCurve(to: CGPoint(x: 6.44, y: 9), controlPoint1: CGPoint(x: 8, y: 9.33), controlPoint2: CGPoint(x: 7.57, y: 9))
bezierPath.addLine(to: CGPoint(x: 2.09, y: 9))
bezierPath.addCurve(to: CGPoint(x: 1, y: 10.05), controlPoint1: CGPoint(x: 1.46, y: 9), controlPoint2: CGPoint(x: 1, y: 9.43))
bezierPath.addCurve(to: CGPoint(x: 7.7, y: 23.62), controlPoint1: CGPoint(x: 1, y: 11.2), controlPoint2: CGPoint(x: 2.36, y: 16.51))
bezierPath.addCurve(to: CGPoint(x: 20.23, y: 31), controlPoint1: CGPoint(x: 11.28, y: 28.41), controlPoint2: CGPoint(x: 15.99, y: 31))
bezierPath.addCurve(to: CGPoint(x: 23.45, y: 29.49), controlPoint1: CGPoint(x: 22.82, y: 31), controlPoint2: CGPoint(x: 23.45, y: 30.57))
bezierPath.addLine(to: CGPoint(x: 23.45, y: 25.82))
bezierPath.addCurve(to: CGPoint(x: 24.34, y: 24.51), controlPoint1: CGPoint(x: 23.45, y: 24.9), controlPoint2: CGPoint(x: 23.81, y: 24.51))
bezierPath.addCurve(to: CGPoint(x: 28.46, y: 27.1), controlPoint1: CGPoint(x: 24.94, y: 24.51), controlPoint2: CGPoint(x: 25.99, y: 24.7))
bezierPath.addCurve(to: CGPoint(x: 33.16, y: 31), controlPoint1: CGPoint(x: 31.37, y: 29.85), controlPoint2: CGPoint(x: 31.57, y: 31))
bezierPath.addLine(to: CGPoint(x: 38.04, y: 31))
bezierPath.addCurve(to: CGPoint(x: 39, y: 29.95), controlPoint1: CGPoint(x: 38.54, y: 31), controlPoint2: CGPoint(x: 39, y: 30.77))
bezierPath.addCurve(to: CGPoint(x: 35.39, y: 24.64), controlPoint1: CGPoint(x: 39, y: 28.87), controlPoint2: CGPoint(x: 37.57, y: 26.93))
bezierPath.addCurve(to: CGPoint(x: 32.57, y: 21.59), controlPoint1: CGPoint(x: 34.49, y: 23.46), controlPoint2: CGPoint(x: 33.03, y: 22.18))
bezierPath.addCurve(to: CGPoint(x: 32.57, y: 19.79), controlPoint1: CGPoint(x: 31.9, y: 20.9), controlPoint2: CGPoint(x: 32.1, y: 20.51))
bezierPath.close()
bezierPath.usesEvenOddFillRule = true
fillColor.setFill()
bezierPath.fill()
context.restoreGState()
}
@objc dynamic public class func drawWhatsapp(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()!
//// Resize to Target Frame
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 40, height: 40), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 40, y: resizedFrame.height / 40)
//// Bezier Drawing
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 28.52, y: 22.97))
bezierPath.addCurve(to: CGPoint(x: 25.54, y: 21.56), controlPoint1: CGPoint(x: 28.08, y: 22.76), controlPoint2: CGPoint(x: 25.94, y: 21.71))
bezierPath.addCurve(to: CGPoint(x: 24.56, y: 21.78), controlPoint1: CGPoint(x: 25.14, y: 21.42), controlPoint2: CGPoint(x: 24.85, y: 21.35))
bezierPath.addCurve(to: CGPoint(x: 23.18, y: 23.48), controlPoint1: CGPoint(x: 24.27, y: 22.21), controlPoint2: CGPoint(x: 23.44, y: 23.19))
bezierPath.addCurve(to: CGPoint(x: 22.24, y: 23.59), controlPoint1: CGPoint(x: 22.93, y: 23.77), controlPoint2: CGPoint(x: 22.68, y: 23.81))
bezierPath.addCurve(to: CGPoint(x: 18.74, y: 21.44), controlPoint1: CGPoint(x: 21.81, y: 23.37), controlPoint2: CGPoint(x: 20.4, y: 22.91))
bezierPath.addCurve(to: CGPoint(x: 16.32, y: 18.44), controlPoint1: CGPoint(x: 17.44, y: 20.29), controlPoint2: CGPoint(x: 16.57, y: 18.87))
bezierPath.addCurve(to: CGPoint(x: 16.51, y: 17.55), controlPoint1: CGPoint(x: 16.06, y: 18), controlPoint2: CGPoint(x: 16.29, y: 17.77))
bezierPath.addCurve(to: CGPoint(x: 17.16, y: 16.79), controlPoint1: CGPoint(x: 16.7, y: 17.36), controlPoint2: CGPoint(x: 16.94, y: 17.05))
bezierPath.addCurve(to: CGPoint(x: 17.6, y: 16.07), controlPoint1: CGPoint(x: 17.38, y: 16.54), controlPoint2: CGPoint(x: 17.45, y: 16.36))
bezierPath.addCurve(to: CGPoint(x: 17.56, y: 15.31), controlPoint1: CGPoint(x: 17.74, y: 15.78), controlPoint2: CGPoint(x: 17.67, y: 15.53))
bezierPath.addCurve(to: CGPoint(x: 16.22, y: 12.09), controlPoint1: CGPoint(x: 17.45, y: 15.09), controlPoint2: CGPoint(x: 16.58, y: 12.96))
bezierPath.addCurve(to: CGPoint(x: 15.24, y: 11.35), controlPoint1: CGPoint(x: 15.86, y: 11.25), controlPoint2: CGPoint(x: 15.51, y: 11.36))
bezierPath.addCurve(to: CGPoint(x: 14.4, y: 11.33), controlPoint1: CGPoint(x: 14.98, y: 11.34), controlPoint2: CGPoint(x: 14.69, y: 11.33))
bezierPath.addCurve(to: CGPoint(x: 13.24, y: 11.88), controlPoint1: CGPoint(x: 14.11, y: 11.33), controlPoint2: CGPoint(x: 13.64, y: 11.44))
bezierPath.addCurve(to: CGPoint(x: 11.72, y: 15.49), controlPoint1: CGPoint(x: 12.84, y: 12.31), controlPoint2: CGPoint(x: 11.72, y: 13.36))
bezierPath.addCurve(to: CGPoint(x: 13.5, y: 19.97), controlPoint1: CGPoint(x: 11.72, y: 17.62), controlPoint2: CGPoint(x: 13.28, y: 19.68))
bezierPath.addCurve(to: CGPoint(x: 20.93, y: 26.52), controlPoint1: CGPoint(x: 13.71, y: 20.26), controlPoint2: CGPoint(x: 16.57, y: 24.64))
bezierPath.addCurve(to: CGPoint(x: 23.42, y: 27.43), controlPoint1: CGPoint(x: 21.97, y: 26.96), controlPoint2: CGPoint(x: 22.78, y: 27.23))
bezierPath.addCurve(to: CGPoint(x: 26.16, y: 27.6), controlPoint1: CGPoint(x: 24.46, y: 27.76), controlPoint2: CGPoint(x: 25.41, y: 27.71))
bezierPath.addCurve(to: CGPoint(x: 29.1, y: 25.54), controlPoint1: CGPoint(x: 27, y: 27.48), controlPoint2: CGPoint(x: 28.74, y: 26.55))
bezierPath.addCurve(to: CGPoint(x: 29.35, y: 23.48), controlPoint1: CGPoint(x: 29.46, y: 24.53), controlPoint2: CGPoint(x: 29.46, y: 23.66))
bezierPath.addCurve(to: CGPoint(x: 28.52, y: 22.97), controlPoint1: CGPoint(x: 29.24, y: 23.3), controlPoint2: CGPoint(x: 28.95, y: 23.19))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 20.57, y: 33.77))
bezierPath.addLine(to: CGPoint(x: 20.57, y: 33.77))
bezierPath.addCurve(to: CGPoint(x: 13.2, y: 31.76), controlPoint1: CGPoint(x: 17.97, y: 33.77), controlPoint2: CGPoint(x: 15.42, y: 33.07))
bezierPath.addLine(to: CGPoint(x: 12.67, y: 31.45))
bezierPath.addLine(to: CGPoint(x: 7.18, y: 32.88))
bezierPath.addLine(to: CGPoint(x: 8.65, y: 27.56))
bezierPath.addLine(to: CGPoint(x: 8.3, y: 27.01))
bezierPath.addCurve(to: CGPoint(x: 6.09, y: 19.34), controlPoint1: CGPoint(x: 6.85, y: 24.72), controlPoint2: CGPoint(x: 6.09, y: 22.07))
bezierPath.addCurve(to: CGPoint(x: 20.58, y: 4.93), controlPoint1: CGPoint(x: 6.09, y: 11.4), controlPoint2: CGPoint(x: 12.59, y: 4.93))
bezierPath.addCurve(to: CGPoint(x: 30.82, y: 9.16), controlPoint1: CGPoint(x: 24.45, y: 4.93), controlPoint2: CGPoint(x: 28.08, y: 6.43))
bezierPath.addCurve(to: CGPoint(x: 35.06, y: 19.35), controlPoint1: CGPoint(x: 33.55, y: 11.88), controlPoint2: CGPoint(x: 35.06, y: 15.5))
bezierPath.addCurve(to: CGPoint(x: 20.57, y: 33.77), controlPoint1: CGPoint(x: 35.05, y: 27.3), controlPoint2: CGPoint(x: 28.56, y: 33.77))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 32.9, y: 7.09))
bezierPath.addCurve(to: CGPoint(x: 20.57, y: 2), controlPoint1: CGPoint(x: 29.61, y: 3.81), controlPoint2: CGPoint(x: 25.23, y: 2))
bezierPath.addCurve(to: CGPoint(x: 3.15, y: 19.34), controlPoint1: CGPoint(x: 10.97, y: 2), controlPoint2: CGPoint(x: 3.15, y: 9.78))
bezierPath.addCurve(to: CGPoint(x: 5.47, y: 28.01), controlPoint1: CGPoint(x: 3.14, y: 22.4), controlPoint2: CGPoint(x: 3.95, y: 25.38))
bezierPath.addLine(to: CGPoint(x: 3, y: 37))
bezierPath.addLine(to: CGPoint(x: 12.24, y: 34.59))
bezierPath.addCurve(to: CGPoint(x: 20.57, y: 36.7), controlPoint1: CGPoint(x: 14.78, y: 35.97), controlPoint2: CGPoint(x: 17.65, y: 36.7))
bezierPath.addLine(to: CGPoint(x: 20.57, y: 36.7))
bezierPath.addLine(to: CGPoint(x: 20.57, y: 36.7))
bezierPath.addCurve(to: CGPoint(x: 38, y: 19.36), controlPoint1: CGPoint(x: 30.18, y: 36.7), controlPoint2: CGPoint(x: 38, y: 28.92))
bezierPath.addCurve(to: CGPoint(x: 32.9, y: 7.09), controlPoint1: CGPoint(x: 38, y: 14.72), controlPoint2: CGPoint(x: 36.19, y: 10.36))
bezierPath.close()
bezierPath.usesEvenOddFillRule = true
fillColor.setFill()
bezierPath.fill()
context.restoreGState()
}
@objc dynamic public class func drawTelegram(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()!
//// Resize to Target Frame
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 40, height: 40), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 40, y: resizedFrame.height / 40)
//// Bezier Drawing
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 35.15, y: 4.5))
bezierPath.addCurve(to: CGPoint(x: 33.94, y: 4.79), controlPoint1: CGPoint(x: 34.74, y: 4.51), controlPoint2: CGPoint(x: 34.32, y: 4.62))
bezierPath.addCurve(to: CGPoint(x: 18.74, y: 10.7), controlPoint1: CGPoint(x: 33.25, y: 5.06), controlPoint2: CGPoint(x: 26, y: 7.88))
bezierPath.addCurve(to: CGPoint(x: 8.69, y: 14.6), controlPoint1: CGPoint(x: 15.11, y: 12.12), controlPoint2: CGPoint(x: 11.49, y: 13.52))
bezierPath.addCurve(to: CGPoint(x: 3.67, y: 16.54), controlPoint1: CGPoint(x: 5.9, y: 15.69), controlPoint2: CGPoint(x: 3.87, y: 16.47))
bezierPath.addCurve(to: CGPoint(x: 1.4, y: 17.91), controlPoint1: CGPoint(x: 3.01, y: 16.77), controlPoint2: CGPoint(x: 2.05, y: 17.15))
bezierPath.addCurve(to: CGPoint(x: 1.12, y: 19.55), controlPoint1: CGPoint(x: 1.08, y: 18.28), controlPoint2: CGPoint(x: 0.84, y: 18.97))
bezierPath.addCurve(to: CGPoint(x: 2.72, y: 20.75), controlPoint1: CGPoint(x: 1.41, y: 20.13), controlPoint2: CGPoint(x: 1.95, y: 20.46))
bezierPath.addLine(to: CGPoint(x: 2.74, y: 20.76))
bezierPath.addLine(to: CGPoint(x: 2.76, y: 20.77))
bezierPath.addCurve(to: CGPoint(x: 10.35, y: 23.15), controlPoint1: CGPoint(x: 5.56, y: 21.64), controlPoint2: CGPoint(x: 9.78, y: 22.97))
bezierPath.addCurve(to: CGPoint(x: 13.25, y: 32.72), controlPoint1: CGPoint(x: 10.5, y: 23.63), controlPoint2: CGPoint(x: 12.28, y: 29.53))
bezierPath.addCurve(to: CGPoint(x: 14.93, y: 33.73), controlPoint1: CGPoint(x: 13.5, y: 33.38), controlPoint2: CGPoint(x: 14.24, y: 33.82))
bezierPath.addCurve(to: CGPoint(x: 15.53, y: 33.67), controlPoint1: CGPoint(x: 15.05, y: 33.73), controlPoint2: CGPoint(x: 15.28, y: 33.73))
bezierPath.addCurve(to: CGPoint(x: 16.83, y: 32.94), controlPoint1: CGPoint(x: 15.9, y: 33.58), controlPoint2: CGPoint(x: 16.38, y: 33.37))
bezierPath.addLine(to: CGPoint(x: 16.83, y: 32.94))
bezierPath.addCurve(to: CGPoint(x: 20.46, y: 29.4), controlPoint1: CGPoint(x: 17.48, y: 32.33), controlPoint2: CGPoint(x: 19.78, y: 30.07))
bezierPath.addLine(to: CGPoint(x: 27.96, y: 34.96))
bezierPath.addLine(to: CGPoint(x: 27.99, y: 34.98))
bezierPath.addCurve(to: CGPoint(x: 29.57, y: 35.49), controlPoint1: CGPoint(x: 27.99, y: 34.98), controlPoint2: CGPoint(x: 28.67, y: 35.43))
bezierPath.addCurve(to: CGPoint(x: 31.04, y: 35.09), controlPoint1: CGPoint(x: 30.01, y: 35.52), controlPoint2: CGPoint(x: 30.56, y: 35.44))
bezierPath.addCurve(to: CGPoint(x: 32.02, y: 33.42), controlPoint1: CGPoint(x: 31.51, y: 34.75), controlPoint2: CGPoint(x: 31.85, y: 34.17))
bezierPath.addCurve(to: CGPoint(x: 37.28, y: 8.61), controlPoint1: CGPoint(x: 32.61, y: 30.86), controlPoint2: CGPoint(x: 36.6, y: 11.79))
bezierPath.addCurve(to: CGPoint(x: 36.71, y: 5.03), controlPoint1: CGPoint(x: 37.71, y: 6.99), controlPoint2: CGPoint(x: 37.51, y: 5.73))
bezierPath.addCurve(to: CGPoint(x: 35.33, y: 4.5), controlPoint1: CGPoint(x: 36.3, y: 4.68), controlPoint2: CGPoint(x: 35.82, y: 4.52))
bezierPath.addCurve(to: CGPoint(x: 35.15, y: 4.5), controlPoint1: CGPoint(x: 35.27, y: 4.5), controlPoint2: CGPoint(x: 35.21, y: 4.5))
bezierPath.addLine(to: CGPoint(x: 35.15, y: 4.5))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 35.24, y: 6.07))
bezierPath.addCurve(to: CGPoint(x: 35.68, y: 6.21), controlPoint1: CGPoint(x: 35.43, y: 6.07), controlPoint2: CGPoint(x: 35.59, y: 6.12))
bezierPath.addCurve(to: CGPoint(x: 35.77, y: 8.23), controlPoint1: CGPoint(x: 35.88, y: 6.38), controlPoint2: CGPoint(x: 36.13, y: 6.89))
bezierPath.addCurve(to: CGPoint(x: 30.51, y: 33.07), controlPoint1: CGPoint(x: 33.84, y: 16.86), controlPoint2: CGPoint(x: 32.16, y: 25.68))
bezierPath.addCurve(to: CGPoint(x: 30.12, y: 33.83), controlPoint1: CGPoint(x: 30.4, y: 33.57), controlPoint2: CGPoint(x: 30.24, y: 33.75))
bezierPath.addCurve(to: CGPoint(x: 29.67, y: 33.94), controlPoint1: CGPoint(x: 30, y: 33.92), controlPoint2: CGPoint(x: 29.87, y: 33.95))
bezierPath.addCurve(to: CGPoint(x: 28.82, y: 33.66), controlPoint1: CGPoint(x: 29.28, y: 33.91), controlPoint2: CGPoint(x: 28.84, y: 33.67))
bezierPath.addLine(to: CGPoint(x: 16.66, y: 24.63))
bezierPath.addCurve(to: CGPoint(x: 30.24, y: 11.88), controlPoint1: CGPoint(x: 17.86, y: 23.5), controlPoint2: CGPoint(x: 25.86, y: 15.9))
bezierPath.addCurve(to: CGPoint(x: 29.69, y: 10.6), controlPoint1: CGPoint(x: 30.72, y: 11.42), controlPoint2: CGPoint(x: 30.33, y: 10.59))
bezierPath.addCurve(to: CGPoint(x: 27.45, y: 11.72), controlPoint1: CGPoint(x: 28.87, y: 10.76), controlPoint2: CGPoint(x: 28.19, y: 11.35))
bezierPath.addCurve(to: CGPoint(x: 10.91, y: 21.68), controlPoint1: CGPoint(x: 22.04, y: 14.9), controlPoint2: CGPoint(x: 11.81, y: 21.13))
bezierPath.addCurve(to: CGPoint(x: 3.26, y: 19.29), controlPoint1: CGPoint(x: 10.44, y: 21.53), controlPoint2: CGPoint(x: 6.11, y: 20.17))
bezierPath.addCurve(to: CGPoint(x: 2.59, y: 18.92), controlPoint1: CGPoint(x: 2.81, y: 19.11), controlPoint2: CGPoint(x: 2.65, y: 18.97))
bezierPath.addCurve(to: CGPoint(x: 2.59, y: 18.92), controlPoint1: CGPoint(x: 2.59, y: 18.92), controlPoint2: CGPoint(x: 2.59, y: 18.92))
bezierPath.addCurve(to: CGPoint(x: 4.19, y: 18.01), controlPoint1: CGPoint(x: 2.8, y: 18.67), controlPoint2: CGPoint(x: 3.67, y: 18.19))
bezierPath.addCurve(to: CGPoint(x: 9.25, y: 16.06), controlPoint1: CGPoint(x: 4.57, y: 17.87), controlPoint2: CGPoint(x: 6.46, y: 17.14))
bezierPath.addCurve(to: CGPoint(x: 19.3, y: 12.16), controlPoint1: CGPoint(x: 12.05, y: 14.98), controlPoint2: CGPoint(x: 15.67, y: 13.57))
bezierPath.addCurve(to: CGPoint(x: 34.56, y: 6.23), controlPoint1: CGPoint(x: 24.39, y: 10.19), controlPoint2: CGPoint(x: 29.48, y: 8.21))
bezierPath.addCurve(to: CGPoint(x: 35.24, y: 6.07), controlPoint1: CGPoint(x: 34.81, y: 6.11), controlPoint2: CGPoint(x: 35.04, y: 6.06))
bezierPath.addLine(to: CGPoint(x: 35.24, y: 6.07))
bezierPath.close()
bezierPath.move(to: CGPoint(x: 16.15, y: 26.2))
bezierPath.addLine(to: CGPoint(x: 19.19, y: 28.45))
bezierPath.addCurve(to: CGPoint(x: 15.79, y: 31.79), controlPoint1: CGPoint(x: 18.35, y: 29.29), controlPoint2: CGPoint(x: 16.35, y: 31.26))
bezierPath.addLine(to: CGPoint(x: 16.15, y: 26.2))
bezierPath.addLine(to: CGPoint(x: 16.15, y: 26.2))
bezierPath.close()
fillColor.setFill()
bezierPath.fill()
context.restoreGState()
}
@objc dynamic public class func drawFacebook(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 41, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()!
//// Resize to Target Frame
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 41, height: 40), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 41, y: resizedFrame.height / 40)
//// Bezier Drawing
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 22.36, y: 21.49))
bezierPath.addLine(to: CGPoint(x: 27.52, y: 21.49))
bezierPath.addLine(to: CGPoint(x: 28.29, y: 15.45))
bezierPath.addLine(to: CGPoint(x: 22.36, y: 15.45))
bezierPath.addLine(to: CGPoint(x: 22.36, y: 11.59))
bezierPath.addCurve(to: CGPoint(x: 25.33, y: 8.65), controlPoint1: CGPoint(x: 22.36, y: 9.84), controlPoint2: CGPoint(x: 22.84, y: 8.65))
bezierPath.addLine(to: CGPoint(x: 28.5, y: 8.64))
bezierPath.addLine(to: CGPoint(x: 28.5, y: 3.24))
bezierPath.addCurve(to: CGPoint(x: 23.88, y: 3), controlPoint1: CGPoint(x: 27.95, y: 3.16), controlPoint2: CGPoint(x: 26.07, y: 3))
bezierPath.addCurve(to: CGPoint(x: 16.17, y: 10.99), controlPoint1: CGPoint(x: 19.3, y: 3), controlPoint2: CGPoint(x: 16.17, y: 5.82))
bezierPath.addLine(to: CGPoint(x: 16.17, y: 15.45))
bezierPath.addLine(to: CGPoint(x: 11, y: 15.45))
bezierPath.addLine(to: CGPoint(x: 11, y: 21.49))
bezierPath.addLine(to: CGPoint(x: 16.17, y: 21.49))
bezierPath.addLine(to: CGPoint(x: 16.17, y: 37))
bezierPath.addLine(to: CGPoint(x: 22.36, y: 37))
bezierPath.addLine(to: CGPoint(x: 22.36, y: 21.49))
bezierPath.close()
bezierPath.usesEvenOddFillRule = true
fillColor.setFill()
bezierPath.fill()
context.restoreGState()
}
@objc dynamic public class func drawViber(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 41, height: 40), resizing: ResizingBehavior = .aspectFit, fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()!
//// Resize to Target Frame
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 41, height: 40), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 41, y: resizedFrame.height / 40)
//// Bezier 5 Drawing
let bezier5Path = UIBezierPath()
bezier5Path.move(to: CGPoint(x: 34, y: 16.95))
bezier5Path.addCurve(to: CGPoint(x: 23.83, y: 6), controlPoint1: CGPoint(x: 34.05, y: 11.59), controlPoint2: CGPoint(x: 29.49, y: 6.68))
bezier5Path.addCurve(to: CGPoint(x: 23.47, y: 5.94), controlPoint1: CGPoint(x: 23.72, y: 5.98), controlPoint2: CGPoint(x: 23.6, y: 5.96))
bezier5Path.addCurve(to: CGPoint(x: 22.61, y: 5.85), controlPoint1: CGPoint(x: 23.19, y: 5.9), controlPoint2: CGPoint(x: 22.9, y: 5.85))
bezier5Path.addCurve(to: CGPoint(x: 21.06, y: 7.15), controlPoint1: CGPoint(x: 21.45, y: 5.85), controlPoint2: CGPoint(x: 21.14, y: 6.66))
bezier5Path.addCurve(to: CGPoint(x: 21.28, y: 8.33), controlPoint1: CGPoint(x: 20.98, y: 7.62), controlPoint2: CGPoint(x: 21.05, y: 8.02))
bezier5Path.addCurve(to: CGPoint(x: 22.88, y: 9.02), controlPoint1: CGPoint(x: 21.67, y: 8.85), controlPoint2: CGPoint(x: 22.34, y: 8.94))
bezier5Path.addCurve(to: CGPoint(x: 23.32, y: 9.09), controlPoint1: CGPoint(x: 23.04, y: 9.04), controlPoint2: CGPoint(x: 23.19, y: 9.06))
bezier5Path.addCurve(to: CGPoint(x: 30.94, y: 17.04), controlPoint1: CGPoint(x: 28.4, y: 10.22), controlPoint2: CGPoint(x: 30.11, y: 12.01))
bezier5Path.addCurve(to: CGPoint(x: 30.98, y: 17.48), controlPoint1: CGPoint(x: 30.96, y: 17.17), controlPoint2: CGPoint(x: 30.97, y: 17.32))
bezier5Path.addCurve(to: CGPoint(x: 32.44, y: 19.33), controlPoint1: CGPoint(x: 31.02, y: 18.08), controlPoint2: CGPoint(x: 31.1, y: 19.33))
bezier5Path.addLine(to: CGPoint(x: 32.44, y: 19.33))
bezier5Path.addCurve(to: CGPoint(x: 32.8, y: 19.3), controlPoint1: CGPoint(x: 32.55, y: 19.33), controlPoint2: CGPoint(x: 32.68, y: 19.32))
bezier5Path.addCurve(to: CGPoint(x: 34, y: 17.42), controlPoint1: CGPoint(x: 34.05, y: 19.11), controlPoint2: CGPoint(x: 34.02, y: 17.97))
bezier5Path.addCurve(to: CGPoint(x: 34, y: 17.02), controlPoint1: CGPoint(x: 33.99, y: 17.26), controlPoint2: CGPoint(x: 33.99, y: 17.12))
bezier5Path.addCurve(to: CGPoint(x: 34, y: 16.95), controlPoint1: CGPoint(x: 34, y: 17), controlPoint2: CGPoint(x: 34, y: 16.97))
bezier5Path.close()
bezier5Path.move(to: CGPoint(x: 22.28, y: 4.03))
bezier5Path.addCurve(to: CGPoint(x: 22.69, y: 4.07), controlPoint1: CGPoint(x: 22.43, y: 4.04), controlPoint2: CGPoint(x: 22.57, y: 4.05))
bezier5Path.addCurve(to: CGPoint(x: 35.93, y: 17.71), controlPoint1: CGPoint(x: 31.03, y: 5.35), controlPoint2: CGPoint(x: 34.87, y: 9.31))
bezier5Path.addCurve(to: CGPoint(x: 35.95, y: 18.22), controlPoint1: CGPoint(x: 35.95, y: 17.86), controlPoint2: CGPoint(x: 35.95, y: 18.03))
bezier5Path.addCurve(to: CGPoint(x: 37.45, y: 20.27), controlPoint1: CGPoint(x: 35.97, y: 18.87), controlPoint2: CGPoint(x: 35.99, y: 20.24))
bezier5Path.addLine(to: CGPoint(x: 37.5, y: 20.27))
bezier5Path.addCurve(to: CGPoint(x: 38.59, y: 19.85), controlPoint1: CGPoint(x: 37.96, y: 20.27), controlPoint2: CGPoint(x: 38.33, y: 20.13))
bezier5Path.addCurve(to: CGPoint(x: 38.99, y: 18.1), controlPoint1: CGPoint(x: 39.04, y: 19.38), controlPoint2: CGPoint(x: 39.01, y: 18.67))
bezier5Path.addCurve(to: CGPoint(x: 38.98, y: 17.71), controlPoint1: CGPoint(x: 38.98, y: 17.96), controlPoint2: CGPoint(x: 38.98, y: 17.82))
bezier5Path.addCurve(to: CGPoint(x: 23.05, y: 1.02), controlPoint1: CGPoint(x: 39.08, y: 9.11), controlPoint2: CGPoint(x: 31.64, y: 1.31))
bezier5Path.addCurve(to: CGPoint(x: 22.95, y: 1.03), controlPoint1: CGPoint(x: 23.01, y: 1.02), controlPoint2: CGPoint(x: 22.98, y: 1.02))
bezier5Path.addCurve(to: CGPoint(x: 22.84, y: 1.03), controlPoint1: CGPoint(x: 22.93, y: 1.03), controlPoint2: CGPoint(x: 22.9, y: 1.03))
bezier5Path.addCurve(to: CGPoint(x: 22.54, y: 1.02), controlPoint1: CGPoint(x: 22.76, y: 1.03), controlPoint2: CGPoint(x: 22.65, y: 1.03))
bezier5Path.addCurve(to: CGPoint(x: 22.1, y: 1), controlPoint1: CGPoint(x: 22.41, y: 1.01), controlPoint2: CGPoint(x: 22.25, y: 1))
bezier5Path.addCurve(to: CGPoint(x: 20.44, y: 2.55), controlPoint1: CGPoint(x: 20.73, y: 1), controlPoint2: CGPoint(x: 20.47, y: 1.97))
bezier5Path.addCurve(to: CGPoint(x: 22.28, y: 4.03), controlPoint1: CGPoint(x: 20.36, y: 3.89), controlPoint2: CGPoint(x: 21.66, y: 3.99))
bezier5Path.close()
bezier5Path.move(to: CGPoint(x: 35.53, y: 28.58))
bezier5Path.addCurve(to: CGPoint(x: 35, y: 28.17), controlPoint1: CGPoint(x: 35.35, y: 28.44), controlPoint2: CGPoint(x: 35.17, y: 28.3))
bezier5Path.addCurve(to: CGPoint(x: 32.18, y: 26.1), controlPoint1: CGPoint(x: 34.09, y: 27.43), controlPoint2: CGPoint(x: 33.12, y: 26.76))
bezier5Path.addCurve(to: CGPoint(x: 31.6, y: 25.7), controlPoint1: CGPoint(x: 31.98, y: 25.97), controlPoint2: CGPoint(x: 31.79, y: 25.83))
bezier5Path.addCurve(to: CGPoint(x: 28.3, y: 24.44), controlPoint1: CGPoint(x: 30.4, y: 24.85), controlPoint2: CGPoint(x: 29.32, y: 24.44))
bezier5Path.addCurve(to: CGPoint(x: 24.74, y: 26.7), controlPoint1: CGPoint(x: 26.92, y: 24.44), controlPoint2: CGPoint(x: 25.73, y: 25.2))
bezier5Path.addCurve(to: CGPoint(x: 23.12, y: 27.68), controlPoint1: CGPoint(x: 24.3, y: 27.36), controlPoint2: CGPoint(x: 23.77, y: 27.68))
bezier5Path.addCurve(to: CGPoint(x: 21.81, y: 27.36), controlPoint1: CGPoint(x: 22.73, y: 27.68), controlPoint2: CGPoint(x: 22.29, y: 27.57))
bezier5Path.addCurve(to: CGPoint(x: 13.59, y: 19.35), controlPoint1: CGPoint(x: 17.93, y: 25.6), controlPoint2: CGPoint(x: 15.17, y: 22.91))
bezier5Path.addCurve(to: CGPoint(x: 14.42, y: 15.59), controlPoint1: CGPoint(x: 12.82, y: 17.63), controlPoint2: CGPoint(x: 13.07, y: 16.51))
bezier5Path.addCurve(to: CGPoint(x: 16.5, y: 12.26), controlPoint1: CGPoint(x: 15.18, y: 15.07), controlPoint2: CGPoint(x: 16.6, y: 14.11))
bezier5Path.addCurve(to: CGPoint(x: 9.8, y: 3.12), controlPoint1: CGPoint(x: 16.39, y: 10.16), controlPoint2: CGPoint(x: 11.75, y: 3.84))
bezier5Path.addCurve(to: CGPoint(x: 7.22, y: 3.11), controlPoint1: CGPoint(x: 8.97, y: 2.82), controlPoint2: CGPoint(x: 8.11, y: 2.81))
bezier5Path.addCurve(to: CGPoint(x: 2.56, y: 6.95), controlPoint1: CGPoint(x: 4.97, y: 3.87), controlPoint2: CGPoint(x: 3.36, y: 5.19))
bezier5Path.addCurve(to: CGPoint(x: 2.66, y: 12.7), controlPoint1: CGPoint(x: 1.78, y: 8.64), controlPoint2: CGPoint(x: 1.82, y: 10.63))
bezier5Path.addCurve(to: CGPoint(x: 12.84, y: 28.19), controlPoint1: CGPoint(x: 5.09, y: 18.67), controlPoint2: CGPoint(x: 8.52, y: 23.89))
bezier5Path.addCurve(to: CGPoint(x: 28.27, y: 38.44), controlPoint1: CGPoint(x: 17.06, y: 32.4), controlPoint2: CGPoint(x: 22.26, y: 35.85))
bezier5Path.addCurve(to: CGPoint(x: 29.8, y: 38.9), controlPoint1: CGPoint(x: 28.82, y: 38.68), controlPoint2: CGPoint(x: 29.39, y: 38.8))
bezier5Path.addCurve(to: CGPoint(x: 30.15, y: 38.98), controlPoint1: CGPoint(x: 29.94, y: 38.93), controlPoint2: CGPoint(x: 30.07, y: 38.95))
bezier5Path.addCurve(to: CGPoint(x: 30.3, y: 39), controlPoint1: CGPoint(x: 30.2, y: 38.99), controlPoint2: CGPoint(x: 30.25, y: 39))
bezier5Path.addLine(to: CGPoint(x: 30.35, y: 39))
bezier5Path.addLine(to: CGPoint(x: 30.35, y: 39))
bezier5Path.addCurve(to: CGPoint(x: 37.63, y: 33.46), controlPoint1: CGPoint(x: 33.18, y: 39), controlPoint2: CGPoint(x: 36.58, y: 36.41))
bezier5Path.addCurve(to: CGPoint(x: 35.53, y: 28.58), controlPoint1: CGPoint(x: 38.54, y: 30.88), controlPoint2: CGPoint(x: 36.87, y: 29.61))
bezier5Path.close()
bezier5Path.move(to: CGPoint(x: 23.53, y: 10.86))
bezier5Path.addCurve(to: CGPoint(x: 21.68, y: 11.93), controlPoint1: CGPoint(x: 23.05, y: 10.87), controlPoint2: CGPoint(x: 22.04, y: 10.9))
bezier5Path.addCurve(to: CGPoint(x: 21.74, y: 13.16), controlPoint1: CGPoint(x: 21.52, y: 12.41), controlPoint2: CGPoint(x: 21.54, y: 12.82))
bezier5Path.addCurve(to: CGPoint(x: 23.14, y: 13.9), controlPoint1: CGPoint(x: 22.04, y: 13.67), controlPoint2: CGPoint(x: 22.62, y: 13.82))
bezier5Path.addCurve(to: CGPoint(x: 26.21, y: 17.21), controlPoint1: CGPoint(x: 25.04, y: 14.21), controlPoint2: CGPoint(x: 26.02, y: 15.26))
bezier5Path.addCurve(to: CGPoint(x: 27.7, y: 18.75), controlPoint1: CGPoint(x: 26.3, y: 18.12), controlPoint2: CGPoint(x: 26.91, y: 18.75))
bezier5Path.addLine(to: CGPoint(x: 27.7, y: 18.75))
bezier5Path.addCurve(to: CGPoint(x: 27.88, y: 18.74), controlPoint1: CGPoint(x: 27.76, y: 18.75), controlPoint2: CGPoint(x: 27.82, y: 18.75))
bezier5Path.addCurve(to: CGPoint(x: 29.24, y: 16.68), controlPoint1: CGPoint(x: 28.82, y: 18.63), controlPoint2: CGPoint(x: 29.28, y: 17.94))
bezier5Path.addCurve(to: CGPoint(x: 27.4, y: 12.69), controlPoint1: CGPoint(x: 29.25, y: 15.37), controlPoint2: CGPoint(x: 28.57, y: 13.88))
bezier5Path.addCurve(to: CGPoint(x: 23.53, y: 10.86), controlPoint1: CGPoint(x: 26.23, y: 11.5), controlPoint2: CGPoint(x: 24.82, y: 10.83))
bezier5Path.close()
fillColor.setFill()
bezier5Path.fill()
context.restoreGState()
}
@objc(SocialIconStyleKitResizingBehavior)
public enum ResizingBehavior: Int {
case aspectFit /// The content is proportionally resized to fit into the target rectangle.
case aspectFill /// The content is proportionally resized to completely fill the target rectangle.
case stretch /// The content is stretched to match the entire target rectangle.
case center /// The content is centered in the target rectangle, but it is NOT resized.
public func apply(rect: CGRect, target: CGRect) -> CGRect {
if rect == target || target == CGRect.zero {
return rect
}
var scales = CGSize.zero
scales.width = abs(target.width / rect.width)
scales.height = abs(target.height / rect.height)
switch self {
case .aspectFit:
scales.width = min(scales.width, scales.height)
scales.height = scales.width
case .aspectFill:
scales.width = max(scales.width, scales.height)
scales.height = scales.width
case .stretch:
break
case .center:
scales.width = 1
scales.height = 1
}
var result = rect.standardized
result.size.width *= scales.width
result.size.height *= scales.height
result.origin.x = target.minX + (target.width - result.width) / 2
result.origin.y = target.minY + (target.height - result.height) / 2
return result
}
}
private override init() {}
}
}
@@ -0,0 +1,235 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension SPCodeDraw {
public class SystemIconPack : NSObject {
private struct Cache {
static let gradient: CGGradient = CGGradient(colorsSpace: nil, colors: [UIColor.red.cgColor, UIColor.red.cgColor] as CFArray, locations: [0, 1])!
}
@objc dynamic public class var gradient: CGGradient { return Cache.gradient }
@objc dynamic public class func drawFavorite(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.478, blue: 1.000, alpha: 1.000)) {
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 100, height: 100), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 100, y: resizedFrame.height / 100)
let bezier2Path = UIBezierPath()
bezier2Path.move(to: CGPoint(x: 49.5, y: 8.22))
bezier2Path.addLine(to: CGPoint(x: 39.17, y: 40.29))
bezier2Path.addLine(to: CGPoint(x: 36.66, y: 40.32))
bezier2Path.addLine(to: CGPoint(x: 6.89, y: 40.68))
bezier2Path.addLine(to: CGPoint(x: 32.78, y: 60.88))
bezier2Path.addLine(to: CGPoint(x: 32.04, y: 63.39))
bezier2Path.addLine(to: CGPoint(x: 23.17, y: 93.18))
bezier2Path.addLine(to: CGPoint(x: 49.5, y: 73.61))
bezier2Path.addLine(to: CGPoint(x: 51.55, y: 75.13))
bezier2Path.addLine(to: CGPoint(x: 75.83, y: 93.19))
bezier2Path.addLine(to: CGPoint(x: 66.22, y: 60.89))
bezier2Path.addLine(to: CGPoint(x: 68.23, y: 59.32))
bezier2Path.addLine(to: CGPoint(x: 92.11, y: 40.68))
bezier2Path.addLine(to: CGPoint(x: 59.83, y: 40.29))
bezier2Path.addLine(to: CGPoint(x: 49.5, y: 8.22))
bezier2Path.close()
bezier2Path.move(to: CGPoint(x: 52.21, y: 5.04))
bezier2Path.addLine(to: CGPoint(x: 62.38, y: 36.61))
bezier2Path.addLine(to: CGPoint(x: 94.17, y: 37))
bezier2Path.addCurve(to: CGPoint(x: 95.84, y: 42.39), controlPoint1: CGPoint(x: 96.9, y: 37.03), controlPoint2: CGPoint(x: 98.04, y: 40.68))
bezier2Path.addLine(to: CGPoint(x: 70.34, y: 62.29))
bezier2Path.addLine(to: CGPoint(x: 79.81, y: 94.1))
bezier2Path.addCurve(to: CGPoint(x: 75.43, y: 97.43), controlPoint1: CGPoint(x: 80.63, y: 96.84), controlPoint2: CGPoint(x: 77.67, y: 99.09))
bezier2Path.addLine(to: CGPoint(x: 49.5, y: 78.16))
bezier2Path.addLine(to: CGPoint(x: 23.57, y: 97.43))
bezier2Path.addCurve(to: CGPoint(x: 19.19, y: 94.1), controlPoint1: CGPoint(x: 21.34, y: 99.09), controlPoint2: CGPoint(x: 18.37, y: 96.83))
bezier2Path.addLine(to: CGPoint(x: 28.66, y: 62.29))
bezier2Path.addLine(to: CGPoint(x: 3.16, y: 42.39))
bezier2Path.addCurve(to: CGPoint(x: 4.83, y: 36.99), controlPoint1: CGPoint(x: 0.96, y: 40.68), controlPoint2: CGPoint(x: 2.1, y: 37.03))
bezier2Path.addLine(to: CGPoint(x: 36.62, y: 36.61))
bezier2Path.addLine(to: CGPoint(x: 46.79, y: 5.04))
bezier2Path.addCurve(to: CGPoint(x: 52.21, y: 5.04), controlPoint1: CGPoint(x: 47.67, y: 2.32), controlPoint2: CGPoint(x: 51.33, y: 2.32))
bezier2Path.close()
color.setFill()
bezier2Path.fill()
context.restoreGState()
}
@objc dynamic public class func drawFavoriteFill(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.478, blue: 1.000, alpha: 1.000)) {
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 100, height: 100), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 100, y: resizedFrame.height / 100)
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 51.78, y: 4.92))
bezierPath.addLine(to: CGPoint(x: 61.96, y: 36.5))
bezierPath.addLine(to: CGPoint(x: 93.75, y: 36.88))
bezierPath.addCurve(to: CGPoint(x: 95.42, y: 42.28), controlPoint1: CGPoint(x: 96.48, y: 36.92), controlPoint2: CGPoint(x: 97.61, y: 40.57))
bezierPath.addLine(to: CGPoint(x: 69.92, y: 62.17))
bezierPath.addLine(to: CGPoint(x: 79.39, y: 93.98))
bezierPath.addCurve(to: CGPoint(x: 75.01, y: 97.32), controlPoint1: CGPoint(x: 80.2, y: 96.72), controlPoint2: CGPoint(x: 77.24, y: 98.98))
bezierPath.addLine(to: CGPoint(x: 49.08, y: 78.04))
bezierPath.addLine(to: CGPoint(x: 23.14, y: 97.32))
bezierPath.addCurve(to: CGPoint(x: 18.76, y: 93.98), controlPoint1: CGPoint(x: 20.91, y: 98.97), controlPoint2: CGPoint(x: 17.95, y: 96.71))
bezierPath.addLine(to: CGPoint(x: 28.24, y: 62.17))
bezierPath.addLine(to: CGPoint(x: 2.73, y: 42.27))
bezierPath.addCurve(to: CGPoint(x: 4.41, y: 36.88), controlPoint1: CGPoint(x: 0.54, y: 40.57), controlPoint2: CGPoint(x: 1.67, y: 36.91))
bezierPath.addLine(to: CGPoint(x: 36.19, y: 36.49))
bezierPath.addLine(to: CGPoint(x: 46.37, y: 4.92))
bezierPath.addCurve(to: CGPoint(x: 51.78, y: 4.92), controlPoint1: CGPoint(x: 47.24, y: 2.2), controlPoint2: CGPoint(x: 50.91, y: 2.2))
bezierPath.close()
color.setFill()
bezierPath.fill()
context.restoreGState()
}
@objc dynamic public class func drawShare(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 100, height: 100), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 100, y: resizedFrame.height / 100)
let clip2Path = UIBezierPath()
clip2Path.move(to: CGPoint(x: 38.59, y: 33.54))
clip2Path.addLine(to: CGPoint(x: 38.59, y: 38.16))
clip2Path.addLine(to: CGPoint(x: 24.9, y: 38.16))
clip2Path.addLine(to: CGPoint(x: 24.9, y: 90.08))
clip2Path.addLine(to: CGPoint(x: 75.1, y: 90.08))
clip2Path.addLine(to: CGPoint(x: 75.1, y: 38.16))
clip2Path.addLine(to: CGPoint(x: 61.41, y: 38.16))
clip2Path.addLine(to: CGPoint(x: 61.41, y: 33.54))
clip2Path.addLine(to: CGPoint(x: 80, y: 33.54))
clip2Path.addLine(to: CGPoint(x: 80, y: 95))
clip2Path.addLine(to: CGPoint(x: 20, y: 95))
clip2Path.addLine(to: CGPoint(x: 20, y: 33.54))
clip2Path.addLine(to: CGPoint(x: 38.59, y: 33.54))
clip2Path.close()
clip2Path.move(to: CGPoint(x: 52.27, y: 61.81))
clip2Path.addLine(to: CGPoint(x: 47.73, y: 61.81))
clip2Path.addLine(to: CGPoint(x: 47.73, y: 14.88))
clip2Path.addLine(to: CGPoint(x: 40.08, y: 22.75))
clip2Path.addLine(to: CGPoint(x: 37.14, y: 19.73))
clip2Path.addLine(to: CGPoint(x: 50, y: 6.5))
clip2Path.addLine(to: CGPoint(x: 62.86, y: 19.73))
clip2Path.addLine(to: CGPoint(x: 59.92, y: 22.75))
clip2Path.addLine(to: CGPoint(x: 52.27, y: 14.88))
clip2Path.addLine(to: CGPoint(x: 52.27, y: 61.81))
clip2Path.close()
color.setFill()
clip2Path.fill()
context.restoreGState()
}
@objc dynamic public class func drawClose(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) {
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 100, height: 100), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 100, y: resizedFrame.height / 100)
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 92.02, y: 22.92))
bezierPath.addLine(to: CGPoint(x: 64.42, y: 50.52))
bezierPath.addLine(to: CGPoint(x: 92.02, y: 78.13))
bezierPath.addCurve(to: CGPoint(x: 92.02, y: 92.99), controlPoint1: CGPoint(x: 96.13, y: 82.23), controlPoint2: CGPoint(x: 96.13, y: 88.89))
bezierPath.addCurve(to: CGPoint(x: 84.59, y: 96.07), controlPoint1: CGPoint(x: 89.97, y: 95.05), controlPoint2: CGPoint(x: 87.28, y: 96.07))
bezierPath.addCurve(to: CGPoint(x: 77.16, y: 92.99), controlPoint1: CGPoint(x: 81.9, y: 96.07), controlPoint2: CGPoint(x: 79.22, y: 95.05))
bezierPath.addLine(to: CGPoint(x: 49.55, y: 65.38))
bezierPath.addLine(to: CGPoint(x: 21.95, y: 92.99))
bezierPath.addCurve(to: CGPoint(x: 14.51, y: 96.07), controlPoint1: CGPoint(x: 19.89, y: 95.05), controlPoint2: CGPoint(x: 17.2, y: 96.07))
bezierPath.addCurve(to: CGPoint(x: 7.08, y: 92.99), controlPoint1: CGPoint(x: 11.82, y: 96.07), controlPoint2: CGPoint(x: 9.13, y: 95.05))
bezierPath.addCurve(to: CGPoint(x: 7.08, y: 78.13), controlPoint1: CGPoint(x: 2.97, y: 88.89), controlPoint2: CGPoint(x: 2.97, y: 82.23))
bezierPath.addLine(to: CGPoint(x: 34.69, y: 50.52))
bezierPath.addLine(to: CGPoint(x: 7.08, y: 22.92))
bezierPath.addCurve(to: CGPoint(x: 7.08, y: 8.04), controlPoint1: CGPoint(x: 2.97, y: 18.8), controlPoint2: CGPoint(x: 2.97, y: 12.15))
bezierPath.addCurve(to: CGPoint(x: 21.94, y: 8.04), controlPoint1: CGPoint(x: 11.18, y: 3.94), controlPoint2: CGPoint(x: 17.84, y: 3.94))
bezierPath.addLine(to: CGPoint(x: 49.55, y: 35.65))
bezierPath.addLine(to: CGPoint(x: 77.16, y: 8.04))
bezierPath.addCurve(to: CGPoint(x: 92.02, y: 8.04), controlPoint1: CGPoint(x: 81.26, y: 3.94), controlPoint2: CGPoint(x: 87.92, y: 3.94))
bezierPath.addCurve(to: CGPoint(x: 92.02, y: 22.92), controlPoint1: CGPoint(x: 96.13, y: 12.15), controlPoint2: CGPoint(x: 96.13, y: 18.8))
bezierPath.close()
color.setFill()
bezierPath.fill()
context.restoreGState()
}
@objc(StyleKitNameResizingBehavior)
public enum ResizingBehavior: Int {
case aspectFit /// The content is proportionally resized to fit into the target rectangle.
case aspectFill /// The content is proportionally resized to completely fill the target rectangle.
case stretch /// The content is stretched to match the entire target rectangle.
case center /// The content is centered in the target rectangle, but it is NOT resized.
public func apply(rect: CGRect, target: CGRect) -> CGRect {
if rect == target || target == CGRect.zero {
return rect
}
var scales = CGSize.zero
scales.width = abs(target.width / rect.width)
scales.height = abs(target.height / rect.height)
switch self {
case .aspectFit:
scales.width = min(scales.width, scales.height)
scales.height = scales.width
case .aspectFill:
scales.width = max(scales.width, scales.height)
scales.height = scales.width
case .stretch:
break
case .center:
scales.width = 1
scales.height = 1
}
var result = rect.standardized
result.size.width *= scales.width
result.size.height *= scales.height
result.origin.x = target.minX + (target.width - result.width) / 2
result.origin.y = target.minY + (target.height - result.height) / 2
return result
}
}
private override init() {}
}
}
@@ -0,0 +1,71 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public struct SPConstraintsAssistent {
static func setEqualSizeConstraint(_ subView: UIView, superVuew: UIView) {
subView.translatesAutoresizingMaskIntoConstraints = false;
let topMarginConstraint = NSLayoutConstraint(
item: subView,
attribute: NSLayoutConstraint.Attribute.topMargin,
relatedBy: NSLayoutConstraint.Relation.equal,
toItem: superVuew,
attribute: NSLayoutConstraint.Attribute.top,
multiplier: 1,
constant: 0)
let bottomMarginConstraint = NSLayoutConstraint(
item: subView,
attribute: NSLayoutConstraint.Attribute.bottomMargin,
relatedBy: NSLayoutConstraint.Relation.equal,
toItem: superVuew,
attribute: NSLayoutConstraint.Attribute.bottom,
multiplier: 1,
constant: 0)
let leadingMarginConstraint = NSLayoutConstraint(
item: subView,
attribute: NSLayoutConstraint.Attribute.leadingMargin,
relatedBy: NSLayoutConstraint.Relation.equal,
toItem: superVuew,
attribute: NSLayoutConstraint.Attribute.leading,
multiplier: 1,
constant: 0)
let trailingMarginConstraint = NSLayoutConstraint(
item: subView,
attribute: NSLayoutConstraint.Attribute.trailingMargin,
relatedBy: NSLayoutConstraint.Relation.equal,
toItem: superVuew,
attribute: NSLayoutConstraint.Attribute.trailing,
multiplier: 1,
constant: 0)
superVuew.addConstraints([
topMarginConstraint, bottomMarginConstraint, leadingMarginConstraint, trailingMarginConstraint
])
}
}
@@ -0,0 +1,29 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
public func delay(_ delay:Double, closure:@escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when) {
closure()
}
}
@@ -0,0 +1,59 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
struct SPDevice {
static var isIphone: Bool {
return UIDevice.current.isIphone
}
static var isIpad: Bool {
return UIDevice.current.isIpad
}
struct Orientation {
static var isPortrait: Bool {
var isPortraitOrientation = true
if UIDevice.current.orientation.isValidInterfaceOrientation {
if UIDevice.current.orientation.isPortrait {
isPortraitOrientation = true
} else {
isPortraitOrientation = false
}
} else {
if UIScreen.main.bounds.width < UIScreen.main.bounds.height {
isPortraitOrientation = true
} else {
isPortraitOrientation = false
}
}
return isPortraitOrientation
}
private init() {}
}
private init() {}
}
@@ -0,0 +1,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
struct SPDownloader {
static func image(link: String, withComplection complection: @escaping (UIImage?) -> ()) {
guard let url = URL(string: link) else {
DispatchQueue.main.async {
complection(nil)
}
return
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else {
DispatchQueue.main.async {
complection(nil)
}
return
}
DispatchQueue.main.async {
complection(image)
}
}.resume()
}
}
@@ -0,0 +1,56 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
extension Array {
func takeElements(count: Int) -> Array {
if (count < self.count) {
return Array(self[0..<count])
}
return Array(self)
}
}
extension Array where Element: Equatable {
mutating func removeDuplicates() {
var result = [Element]()
for value in self {
if result.contains(value) == false {
result.append(value)
}
}
self = result
}
}
extension Array where Element: Hashable {
func after(item: Element) -> Element? {
if let index = self.index(of: item), index + 1 < self.count {
return self[index + 1]
}
return nil
}
}
@@ -0,0 +1,58 @@
// 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 UIBezierPath {
func resizeTo(width: CGFloat) {
let currentWidth = self.bounds.width
let relativeFactor = width / currentWidth
self.apply(CGAffineTransform(scaleX: relativeFactor, y: relativeFactor))
}
func convertToImage(fill: Bool, stroke: Bool, color: UIColor = .black) -> UIImage {
UIGraphicsBeginImageContextWithOptions(CGSize(width: self.bounds.width, height: self.bounds.height), false, 0.0)
let context = UIGraphicsGetCurrentContext()
context!.setStrokeColor(color.cgColor)
context!.setFillColor(color.cgColor)
if stroke {
self.stroke()
}
if fill {
self.fill()
}
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
public struct SPBezierPath {
public static func setContext() {
UIGraphicsBeginImageContextWithOptions(CGSize(width: 1, height: 1), false, 0)
}
public static func endContext() {
UIGraphicsEndImageContext()
}
}
@@ -0,0 +1,26 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
func + (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
@@ -0,0 +1,56 @@
// 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 {
static var displayFrame: CGRect {
let screenSize = UIScreen.main.bounds
return CGRect.init(origin: .zero, size: screenSize.size)
}
var bottomXPosition: CGFloat {
get {
return self.origin.x + self.width
}
set {
self.origin.x = newValue - self.width
}
}
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
}
}
@@ -0,0 +1,37 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension CGSize {
func resize(newWidth: CGFloat) -> CGSize {
let relativeSideSize = self.width / self.height
let newHeight = newWidth / relativeSideSize
return CGSize.init(width: newWidth, height: newHeight)
}
func resize(newHeight: CGFloat) -> CGSize {
let relativeSideSize = self.width / self.height
let newWidth = newHeight * relativeSideSize
return CGSize.init(width: newWidth, height: newHeight)
}
}
@@ -0,0 +1,29 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
extension Collection {
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
@@ -0,0 +1,32 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
extension Date {
func formatted(as dateFormat: String) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = dateFormat
return dateFormatter.string(from: self)
}
}
@@ -0,0 +1,37 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
extension Strideable {
public mutating func setIfMore(when value: Self) {
if self > value {
self = value
}
}
public mutating func setIfFewer(when value: Self) {
if self < value {
self = value
}
}
}
@@ -0,0 +1,86 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
import UIKit
extension String {
mutating func dropLast(substring: String) {
if self.hasSuffix(substring) {
self = String(dropLast(substring.count))
}
}
mutating func dropFirst(substring: String) {
if self.hasPrefix(substring) {
self = String(dropFirst(substring.count))
}
}
func uppercasedFirstLetter() -> String {
let lowercaseSctring = self.lowercased()
return lowercaseSctring.prefix(1).uppercased() + lowercaseSctring.dropFirst()
}
mutating func uppercaseFirstLetter() {
self = self.uppercasedFirstLetter()
}
func removeAllSpaces() -> String {
return self.components(separatedBy: .whitespaces).joined()
}
mutating func removeAllSpaces() {
self = self.removeAllSpaces()
}
var isEmail: Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailTest = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
return emailTest.evaluate(with: self)
}
var isLink: Bool {
if let url = URL(string: self) {
return UIApplication.shared.canOpenURL(url)
}
return false
}
mutating func reduce(minimumFractionDigits: Int = 0, maximumFractionDigits: Int) {
let formatter = NumberFormatter()
formatter.minimumFractionDigits = minimumFractionDigits
formatter.maximumFractionDigits = maximumFractionDigits
let int = Double(self)
if int != nil {
let number = NSNumber.init(value: int!)
if var newValue = formatter.string(from: number) {
newValue.replace(",", with: ".")
self = newValue
}
}
}
mutating func replace(_ replacingString: String, with newString: String) {
self = self.replacingOccurrences(of: replacingString, with: newString)
}
}
@@ -0,0 +1,34 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension UITextField {
@IBInspectable var placeholderColor: UIColor? {
get {
return self.placeholderColor
}
set {
self.attributedPlaceholder = NSAttributedString(string:self.placeholder != nil ? self.placeholder! : "", attributes:[NSAttributedString.Key.foregroundColor: newValue!])
}
}
}
@@ -0,0 +1,103 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension UIAlertController {
static var elementsColor: UIColor {
get {
return UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor
}
set {
UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = newValue
}
}
public static func show(title: String, message: String, buttonTitle: String, cancelButtonTitle: String? = nil, complection: @escaping ()->() = {}, on viewController: UIViewController) {
let ac = UIAlertController(
title: title,
message: message,
preferredStyle: .alert
)
if cancelButtonTitle != nil {
ac.addAction(UIAlertAction.init(
title: cancelButtonTitle!,
style: UIAlertAction.Style.cancel,
handler: nil)
)
}
ac.addAction(UIAlertAction.init(
title: buttonTitle,
style: UIAlertAction.Style.default,
handler: { (action) in
complection()
}))
viewController.present(ac, animated: true, completion: nil)
}
public static func сonfirm(title: String? = nil, message: String, buttonTitle: String, cancelButtonTitle: String, isDestructive: Bool = false, complection: @escaping (Bool)->(), on viewController: UIViewController) {
let ac = UIAlertController(
title: title,
message: message,
preferredStyle: .actionSheet
)
var style = UIAlertAction.Style.default
if isDestructive {
style = .destructive
}
ac.addAction(UIAlertAction.init(
title: buttonTitle,
style: style,
handler: { (action) in
complection(true)
}))
ac.addAction(UIAlertAction.init(
title: cancelButtonTitle,
style: UIAlertAction.Style.default,
handler: { (action) in
complection(false)
}))
viewController.present(ac, animated: true, completion: nil)
}
}
extension UIAlertController {
func addAction(title: String, complection: @escaping ()->()) {
let action = UIAlertAction(title: title, style: .default) { (action) in
complection()
}
self.addAction(action)
}
func addDestructiveAction(title: String, complection: @escaping ()->()) {
let action = UIAlertAction(title: title, style: .destructive) { (action) in
complection()
}
self.addAction(action)
}
}
@@ -0,0 +1,142 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension UIButton {
typealias UIButtonTargetClosure = () -> ()
private class ClosureWrapper: NSObject {
let closure: UIButtonTargetClosure
init(_ closure: @escaping UIButtonTargetClosure) {
self.closure = closure
}
}
private struct AssociatedKeys {
static var targetClosure = "targetClosure"
}
private var targetClosure: UIButtonTargetClosure? {
get {
guard let closureWrapper = objc_getAssociatedObject(self, &AssociatedKeys.targetClosure) as? ClosureWrapper else { return nil }
return closureWrapper.closure
}
set(newValue) {
guard let newValue = newValue else { return }
objc_setAssociatedObject(self, &AssociatedKeys.targetClosure, ClosureWrapper(newValue), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
func target(_ action: @escaping UIButtonTargetClosure) {
targetClosure = action
addTarget(self, action: #selector(UIButton.targetAction), for: .touchUpInside)
}
@objc func targetAction() {
guard let targetClosure = targetClosure else { return }
targetClosure()
}
}
extension UIButton {
func setTitle(_ title: String) {
self.setTitle(title, for: .normal)
}
func setTitleColor(_ color: UIColor) {
self.setTitleColorForNoramlAndHightlightedStates(color: color)
}
func removeAllTargets() {
self.removeTarget(nil, action: nil, for: .allEvents)
}
func showText(_ text: String, withComplection completion: (() -> Void)! = {}) {
let baseText = self.titleLabel?.text ?? " "
SPAnimation.animate(0.2,
animations: {
self.titleLabel?.alpha = 0
}, withComplection: {
self.setTitle(text, for: .normal)
SPAnimation.animate(0.2, animations: {
self.titleLabel?.alpha = 1
}, withComplection: {
SPAnimation.animate(0.2, animations: {
self.titleLabel?.alpha = 0
}, delay: 0.35,
withComplection: {
self.setTitle(baseText, for: .normal)
SPAnimation.animate(0.2, animations: {
self.titleLabel?.alpha = 1
}, withComplection: {
completion()
})
})
})
})
}
func setAnimatableText(_ text: String, withComplection completion: (() -> Void)! = {}) {
SPAnimation.animate(0.2,
animations: {
self.titleLabel?.alpha = 0
}, withComplection: {
self.setTitle(text, for: .normal)
SPAnimation.animate(0.2, animations: {
self.titleLabel?.alpha = 1
}, withComplection: {
completion()
})
})
}
func hideContent(completion: (() -> Void)! = {}) {
SPAnimation.animate(0.2,
animations: {
self.titleLabel?.alpha = 0
}, withComplection: {
completion()
})
}
func showContent(completion: (() -> Void)! = {}) {
SPAnimation.animate(0.2,
animations: {
self.titleLabel?.alpha = 1
}, withComplection: {
completion()
})
}
func setTitleColorForNoramlAndHightlightedStates(color: UIColor) {
self.setTitleColor(color, for: .normal)
self.setTitleColor(color.withAlphaComponent(0.7), for: .highlighted)
}
}
@@ -0,0 +1,32 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension UICollectionView {
var currentIndexCellPath: IndexPath? {
let visibleRect = CGRect(origin: self.contentOffset, size: self.bounds.size)
let visiblePoint = CGPoint.init(x: visibleRect.midX, y: visibleRect.midY)
let visibleIndexPath = self.indexPathForItem(at: visiblePoint)
return visibleIndexPath
}
}
@@ -0,0 +1,68 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public extension UIColor {
convenience init(hex: String) {
var red: CGFloat = 0.0
var green: CGFloat = 0.0
var blue: CGFloat = 0.0
var alpha: CGFloat = 1.0
var hex: String = hex
if hex.hasPrefix("#") {
let index = hex.index(hex.startIndex, offsetBy: 1)
hex = String(hex[index...])
}
let scanner = Scanner(string: hex)
var hexValue: CUnsignedLongLong = 0
if scanner.scanHexInt64(&hexValue) {
switch (hex.count) {
case 3:
red = CGFloat((hexValue & 0xF00) >> 8) / 15.0
green = CGFloat((hexValue & 0x0F0) >> 4) / 15.0
blue = CGFloat(hexValue & 0x00F) / 15.0
case 4:
red = CGFloat((hexValue & 0xF000) >> 12) / 15.0
green = CGFloat((hexValue & 0x0F00) >> 8) / 15.0
blue = CGFloat((hexValue & 0x00F0) >> 4) / 15.0
alpha = CGFloat(hexValue & 0x000F) / 15.0
case 6:
red = CGFloat((hexValue & 0xFF0000) >> 16) / 255.0
green = CGFloat((hexValue & 0x00FF00) >> 8) / 255.0
blue = CGFloat(hexValue & 0x0000FF) / 255.0
case 8:
red = CGFloat((hexValue & 0xFF000000) >> 24) / 255.0
green = CGFloat((hexValue & 0x00FF0000) >> 16) / 255.0
blue = CGFloat((hexValue & 0x0000FF00) >> 8) / 255.0
alpha = CGFloat(hexValue & 0x000000FF) / 255.0
default:
print("SPUIColorExtension - Invalid RGB string, number of characters after '#' should be either 3, 4, 6 or 8", terminator: "")
}
} else {
print("SPUIColorExtension - Scan hex error")
}
self.init(red:red, green:green, blue:blue, alpha:alpha)
}
}
@@ -0,0 +1,33 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public extension UIDevice {
public var isIphone: Bool {
return UIDevice.current.userInterfaceIdiom == .phone
}
public var isIpad: Bool {
return UIDevice.current.userInterfaceIdiom == .pad
}
}
@@ -0,0 +1,111 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public extension UIFont {
public struct fonts {
public static func AvenirNext(type: BoldType, size: CGFloat) -> UIFont {
return UIFont.createFont(.AvenirNext, boldType: type, size: size)
}
}
public static func system(type: BoldType, size: CGFloat) -> UIFont {
if #available(iOS 8.2, *) {
return UIFont.systemFont(ofSize: size, weight: self.getBoldTypeBy(boldType: type))
} else {
return self.createFont(.AvenirNext, boldType: type, size: size)
}
}
public static func createFont(_ fontType: FontType, boldType: BoldType, size: CGFloat) -> UIFont {
return UIFont.init(
name: self.getFontNameBy(fontType: fontType) + self.getBoldTypeNameBy(boldType: boldType),
size: size
)!
}
private static func getFontNameBy(fontType: FontType) -> String {
switch fontType {
case .AvenirNext:
return "AvenirNext"
}
}
private static func getBoldTypeNameBy(boldType: BoldType) -> String {
switch boldType {
case .UltraLight:
return "-UltraLight"
case .Light:
return "-Light"
case .Medium:
return "-Medium"
case .Regular:
return "-Regular"
case .Bold:
return "-Bold"
case .DemiBold:
return "-DemiBold"
default:
return "-Regular"
}
}
@available(iOS 8.2, *)
private static func getBoldTypeBy(boldType: BoldType) -> UIFont.Weight {
switch boldType {
case .UltraLight:
return UIFont.Weight.ultraLight
case .Light:
return UIFont.Weight.light
case .Medium:
return UIFont.Weight.medium
case .Regular:
return UIFont.Weight.regular
case .Bold:
return UIFont.Weight.bold
case .DemiBold:
return UIFont.Weight.semibold
case .Heavy:
return UIFont.Weight.heavy
default:
return UIFont.Weight.regular
}
}
public enum FontType {
case AvenirNext
}
public enum BoldType {
case Regular
case Medium
case Light
case UltraLight
case Heavy
case Bold
case DemiBold
case None
}
}
@@ -0,0 +1,356 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public extension UIImage {
public func resize(newWidth: CGFloat) -> UIImage {
let scale = newWidth / self.size.width
let newHeight = self.size.height * scale
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
self.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
public func resize(to size: CGSize) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0);
self.draw(in: CGRect(origin: CGPoint.zero, size: CGSize(width: size.width, height: size.height)))
let resizeImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return resizeImage
}
public class func drawFromView(view: UIView) -> UIImage {
UIGraphicsBeginImageContext(view.frame.size)
view.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
//MARK: - get assest colors
extension UIImage {
private func resizeForUIImageColors(newSize: CGSize) -> UIImage {
UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
defer {
UIGraphicsEndImageContext()
}
self.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
guard let result = UIGraphicsGetImageFromCurrentImageContext() else {
fatalError("UIImageColors.resizeForUIImageColors failed: UIGraphicsGetImageFromCurrentImageContext returned nil.")
}
return result
}
public func getColors(quality: UIImageAssestColorsQuality = .high, _ completion: @escaping (UIImageAssestColors) -> Void) {
DispatchQueue.global(qos: .background).async {
let result = self.getColors(quality: quality)
DispatchQueue.main.async {
completion(result)
}
}
}
public func getColors(quality: UIImageAssestColorsQuality = .high) -> UIImageAssestColors {
var scaleDownSize: CGSize = self.size
if quality != .highest {
if self.size.width < self.size.height {
let ratio = self.size.height/self.size.width
scaleDownSize = CGSize(width: quality.rawValue/ratio, height: quality.rawValue)
} else {
let ratio = self.size.width/self.size.height
scaleDownSize = CGSize(width: quality.rawValue, height: quality.rawValue/ratio)
}
}
let cgImage = self.resizeForUIImageColors(newSize: scaleDownSize).cgImage!
let width: Int = cgImage.width
let height: Int = cgImage.height
let threshold = Int(CGFloat(height)*0.01)
var proposed: [Double] = [-1,-1,-1,-1]
guard let data = CFDataGetBytePtr(cgImage.dataProvider!.data) else {
fatalError("UIImageColors.getColors failed: could not get cgImage data.")
}
let imageColors = NSCountedSet(capacity: width*height)
for x in 0..<width {
for y in 0..<height {
let pixel: Int = ((width * y) + x) * 4
if 127 <= data[pixel+3] {
imageColors.add((Double(data[pixel+2])*1000000)+(Double(data[pixel+1])*1000)+(Double(data[pixel])))
}
}
}
let sortedColorComparator: Comparator = { (main, other) -> ComparisonResult in
let m = main as! UIImageColorsCounter, o = other as! UIImageColorsCounter
if m.count < o.count {
return .orderedDescending
} else if m.count == o.count {
return .orderedSame
} else {
return .orderedAscending
}
}
var enumerator = imageColors.objectEnumerator()
var sortedColors = NSMutableArray(capacity: imageColors.count)
while let K = enumerator.nextObject() as? Double {
let C = imageColors.count(for: K)
if threshold < C {
sortedColors.add(UIImageColorsCounter(color: K, count: C))
}
}
sortedColors.sort(comparator: sortedColorComparator)
var proposedEdgeColor: UIImageColorsCounter
if 0 < sortedColors.count {
proposedEdgeColor = sortedColors.object(at: 0) as! UIImageColorsCounter
} else {
proposedEdgeColor = UIImageColorsCounter(color: 0, count: 1)
}
if proposedEdgeColor.color.isBlackOrWhite && 0 < sortedColors.count {
for i in 1..<sortedColors.count {
let nextProposedEdgeColor = sortedColors.object(at: i) as! UIImageColorsCounter
if Double(nextProposedEdgeColor.count)/Double(proposedEdgeColor.count) > 0.3 {
if !nextProposedEdgeColor.color.isBlackOrWhite {
proposedEdgeColor = nextProposedEdgeColor
break
}
} else {
break
}
}
}
proposed[0] = proposedEdgeColor.color
enumerator = imageColors.objectEnumerator()
sortedColors.removeAllObjects()
sortedColors = NSMutableArray(capacity: imageColors.count)
let findDarkTextColor = !proposed[0].isDarkColor
while var K = enumerator.nextObject() as? Double {
K = K.with(minSaturation: 0.15)
if K.isDarkColor == findDarkTextColor {
let C = imageColors.count(for: K)
sortedColors.add(UIImageColorsCounter(color: K, count: C))
}
}
sortedColors.sort(comparator: sortedColorComparator)
for color in sortedColors {
let color = (color as! UIImageColorsCounter).color
if proposed[1] == -1 {
if color.isContrasting(proposed[0]) {
proposed[1] = color
}
} else if proposed[2] == -1 {
if !color.isContrasting(proposed[0]) || !proposed[1].isDistinct(color) {
continue
}
proposed[2] = color
} else if proposed[3] == -1 {
if !color.isContrasting(proposed[0]) || !proposed[2].isDistinct(color) || !proposed[1].isDistinct(color) {
continue
}
proposed[3] = color
break
}
}
let isDarkBackground = proposed[0].isDarkColor
for i in 1...3 {
if proposed[i] == -1 {
proposed[i] = isDarkBackground ? 255255255:0
}
}
return UIImageAssestColors(
background: proposed[0].uicolor,
primary: proposed[1].uicolor,
secondary: proposed[2].uicolor,
detail: proposed[3].uicolor
)
}
}
public struct UIImageAssestColors {
public var background: UIColor!
public var primary: UIColor!
public var secondary: UIColor!
public var detail: UIColor!
}
public enum UIImageAssestColorsQuality: CGFloat {
case lowest = 50 // 50px
case low = 100 // 100px
case high = 250 // 250px
case highest = 0 // No scale
}
fileprivate struct UIImageColorsCounter {
let color: Double
let count: Int
init(color: Double, count: Int) {
self.color = color
self.count = count
}
}
fileprivate extension Double {
private var r: Double {
return fmod(floor(self/1000000),1000000)
}
private var g: Double {
return fmod(floor(self/1000),1000)
}
private var b: Double {
return fmod(self,1000)
}
fileprivate var isDarkColor: Bool {
return (r*0.2126) + (g*0.7152) + (b*0.0722) < 127.5
}
fileprivate var isBlackOrWhite: Bool {
return (r > 232 && g > 232 && b > 232) || (r < 23 && g < 23 && b < 23)
}
fileprivate func isDistinct(_ other: Double) -> Bool {
let _r = self.r
let _g = self.g
let _b = self.b
let o_r = other.r
let o_g = other.g
let o_b = other.b
return (fabs(_r-o_r) > 63.75 || fabs(_g-o_g) > 63.75 || fabs(_b-o_b) > 63.75)
&& !(fabs(_r-_g) < 7.65 && fabs(_r-_b) < 7.65 && fabs(o_r-o_g) < 7.65 && fabs(o_r-o_b) < 7.65)
}
fileprivate func with(minSaturation: Double) -> Double {
// Ref: https://en.wikipedia.org/wiki/HSL_and_HSV
// Convert RGB to HSV
let _r = r/255
let _g = g/255
let _b = b/255
var H, S, V: Double
let M = fmax(_r,fmax(_g, _b))
var C = M-fmin(_r,fmin(_g, _b))
V = M
S = V == 0 ? 0:C/V
if minSaturation <= S {
return self
}
if C == 0 {
H = 0
} else if _r == M {
H = fmod((_g-_b)/C, 6)
} else if _g == M {
H = 2+((_b-_r)/C)
} else {
H = 4+((_r-_g)/C)
}
if H < 0 {
H += 6
}
// Back to RGB
C = V*minSaturation
let X = C*(1-fabs(fmod(H,2)-1))
var R, G, B: Double
switch H {
case 0...1:
R = C
G = X
B = 0
case 1...2:
R = X
G = C
B = 0
case 2...3:
R = 0
G = C
B = X
case 3...4:
R = 0
G = X
B = C
case 4...5:
R = X
G = 0
B = C
case 5..<6:
R = C
G = 0
B = X
default:
R = 0
G = 0
B = 0
}
let m = V-C
return (floor((R + m)*255)*1000000)+(floor((G + m)*255)*1000)+floor((B + m)*255)
}
fileprivate func isContrasting(_ color: Double) -> Bool {
let bgLum = (0.2126*r)+(0.7152*g)+(0.0722*b)+12.75
let fgLum = (0.2126*color.r)+(0.7152*color.g)+(0.0722*color.b)+12.75
if bgLum > fgLum {
return 1.6 < bgLum/fgLum
} else {
return 1.6 < fgLum/bgLum
}
}
fileprivate var uicolor: UIColor {
return UIColor(red: CGFloat(r)/255, green: CGFloat(g)/255, blue: CGFloat(b)/255, alpha: 1)
}
fileprivate var pretty: String {
return "\(Int(self.r)), \(Int(self.g)), \(Int(self.b))"
}
}
@@ -0,0 +1,60 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension UIImageView {
public func setNativeStyle() {
self.layer.borderWidth = 0.5
self.layer.borderColor = SPNativeStyleKit.Colors.midGray.cgColor
self.layer.masksToBounds = true
}
public func removeNativeStyle() {
self.layer.borderWidth = 0
self.layer.borderColor = UIColor.clear.cgColor
}
public func downloadedFrom(url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit, withComplection complection: @escaping (UIImage?) -> () = {_ in }) {
DispatchQueue.main.async {
self.contentMode = mode
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { complection(nil); return }
DispatchQueue.main.async() { () -> Void in
self.image = image
complection(image)
}
}.resume()
}
public func downloadedFrom(link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit, withComplection complection: @escaping (UIImage?) -> () = {_ in }) {
guard let url = URL(string: link) else { return }
downloadedFrom(url: url, contentMode: mode, withComplection: complection)
}
}
@@ -0,0 +1,56 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public extension UILabel {
func setShadowOffsetForLetters(blurRadius: CGFloat = 0, widthOffset: CGFloat = 0, heightOffset: CGFloat, opacity: CGFloat) {
self.layer.shadowRadius = blurRadius
self.layer.shadowOffset = CGSize(
width: widthOffset,
height: heightOffset
)
self.layer.shadowOpacity = Float(opacity)
}
func setShadowOffsetFactorForLetters(blurRadius: CGFloat = 0, widthOffsetFactor: CGFloat = 0, heightOffsetFactor: CGFloat, opacity: CGFloat) {
let widthOffset = widthOffsetFactor * self.frame.width
let heightOffset = heightOffsetFactor * self.frame.height
self.setShadowOffsetForLetters(blurRadius: blurRadius, widthOffset: widthOffset, heightOffset: heightOffset, opacity: opacity)
}
func removeShadowForLetters() {
self.setShadowOffsetForLetters(blurRadius: 0, widthOffset: 0, heightOffset: 0, opacity: 0)
}
func setCenteringAlignment() {
self.textAlignment = .center
self.baselineAdjustment = .alignCenters
}
func setLettersSpacing(_ value: CGFloat) {
if let textString = text {
let attrs: [NSAttributedString.Key : Any] = [.kern: value]
attributedText = NSAttributedString(string: textString, attributes: attrs)
}
}
}
@@ -0,0 +1,34 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension UINavigationController {
static var elementsColor: UIColor {
get {
return UINavigationBar.appearance().tintColor
}
set {
UINavigationBar.appearance().tintColor = newValue
}
}
}
@@ -0,0 +1,33 @@
// 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 UIScreen {
var minSideSize: CGFloat {
return min(UIScreen.main.bounds.width, UIScreen.main.bounds.height)
}
var widthLessThanHeight: Bool {
return UIScreen.main.bounds.width < UIScreen.main.bounds.height
}
}
@@ -0,0 +1,72 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension UITabBarController {
func addTabBarItem(titleName: String, imageName: String, viewController: UIViewController) {
let image = UIImage.init(named: imageName)
self.addTabBarItem(
titleName: titleName,
image: image ?? UIImage(),
viewController: viewController
)
}
func addTabBarItem(titleName: String, image: UIImage, viewController: UIViewController) {
let tabBarItem = UITabBarItem(
title: titleName,
image: image,
selectedImage: image
)
viewController.tabBarItem = tabBarItem
if self.viewControllers == nil {
self.viewControllers = [viewController]
} else {
self.viewControllers?.append(viewController)
}
}
@objc func addTabBarItem(titleName: String, image: UIImage, selectedImage: UIImage, viewController: UIViewController) {
let tabBarItem = UITabBarItem(
title: titleName,
image: image,
selectedImage: selectedImage
)
viewController.tabBarItem = tabBarItem
if self.viewControllers == nil {
self.viewControllers = [viewController]
} else {
self.viewControllers?.append(viewController)
}
}
}
@@ -0,0 +1,85 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension UITableView {
var isEmpty: Bool {
return self.lastSectionWithRows == nil
}
func isEmpty(section: Int) -> Bool {
return self.numberOfRows(inSection: section) == 0
}
var lastSection: Int {
return self.numberOfSections - 1
}
var lastSectionWithRows: Int? {
if self.numberOfSections == 0 {
return nil
}
var section = self.numberOfSections - 1
if section < 0 {
return nil
}
while section >= 0 {
if self.numberOfRows(inSection: section) != 0 {
return section
}
section -= 1
}
return nil
}
var firstSectionWithRows: Int? {
if self.numberOfSections == 0 {
return nil
}
var section = 0
if section > self.numberOfSections - 1 {
return nil
}
while section <= (self.numberOfSections - 1) {
if self.numberOfRows(inSection: section) != 0 {
return section
}
section += 1
}
return nil
}
}
@@ -0,0 +1,45 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension UITableViewCell {
public var accessoryView: UIView? {
return subviews.compactMap { $0 as? UIButton }.first
}
var highlightedColor: UIColor? {
get {
return self.backgroundView?.backgroundColor
}
set {
let backgroundView = UIView()
backgroundView.backgroundColor = SPNativeStyleKit.Colors.customGray
self.selectedBackgroundView = backgroundView
}
}
public func highlight() {
self.setHighlighted(true, animated: false)
self.setHighlighted(false, animated: true)
}
}
@@ -0,0 +1,34 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension UITableViewController {
func refreshManually() {
self.refreshControl?.beginRefreshing()
self.tableView.setContentOffset(
CGPoint.init(
x: 0,
y: self.tableView.contentOffset.y - (self.refreshControl?.frame.size.height ?? 0)
), animated: true)
}
}
@@ -0,0 +1,39 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension UITextField {
var isEmptyText: Bool {
get {
if self.text == "" {
return true
}
if self.text == nil {
return true
}
return false
}
}
}
@@ -0,0 +1,175 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
import Photos
extension UIViewController {
func present(_ viewControllerToPresent: UIViewController, completion: (() -> Swift.Void)? = nil) {
self.present(viewControllerToPresent, animated: true, completion: completion)
}
@objc func dismiss() {
self.dismiss(animated: true, completion: nil)
}
func wrapToNavigationController(statusBar: SPStatusBar = .dark) -> UINavigationController {
let controller = SPStatusBarManagerNavigationController(rootViewController: self)
controller.statusBar = statusBar
return controller
}
}
//MARK: - Keyboard
extension UIViewController {
func dismissKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
@objc func dismissKeyboard() {
view.endEditing(true)
}
}
//MARK: - Add image to Library
extension UIViewController {
func save(image: UIImage) {
if SPPermission.isAllow(.photoLibrary) {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)
} else {
SPPermission.request(.photoLibrary) {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)
}
}
}
func saveVideo(url: String, complection: @escaping (Bool)->()) {
DispatchQueue.global(qos: .utility).async {
let urls = URL(string: url)
let urldata = try? Data(contentsOf: urls!)
if urldata != nil {
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let filepath = "\(documentsPath)/tempfile.mp4"
DispatchQueue.main.async {
let urlsave = URL(fileURLWithPath: filepath)
do {
try urldata!.write(to: urlsave, options: Data.WritingOptions.atomic)
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: urlsave)
}, completionHandler: { (status, error) in
try? FileManager.default.removeItem(atPath: filepath)
DispatchQueue.main.async {
complection(error == nil)
}
})
} catch {
try? FileManager.default.removeItem(atPath: filepath)
complection(false)
}
}
}
}
}
@objc func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
if let _ = error {
self.imageSaved(isSuccses: false)
} else {
self.imageSaved(isSuccses: true)
}
}
@objc func imageSaved(isSuccses: Bool) {
fatalError("SPUIViewControllerExtenshion - Need ovveride 'imageSaved' func")
}
}
//MARK: - Navigation Bar
extension UIViewController {
func setPrefersLargeNavigationTitle(_ title: String, smallScreenToSmallBar: Bool = true) {
self.navigationItem.title = title
if #available(iOS 11.0, *) {
self.navigationItem.largeTitleDisplayMode = .automatic
self.navigationController?.navigationBar.prefersLargeTitles = true
}
if smallScreenToSmallBar {
if self.view.frame.width < 375 {
self.setNavigationTitle(title, style: .small)
}
}
}
func setNavigationTitle(_ title: String, style: SPNavigationTitleStyle) {
self.navigationItem.title = title
switch style {
case .large:
if #available(iOS 11.0, *) {
self.navigationItem.largeTitleDisplayMode = .always
}
case .small:
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 navigationBarHeight: CGFloat {
return self.navigationController?.navigationBar.frame.height ?? 0
}
var statusBarHeight: CGFloat {
return UIApplication.shared.statusBarFrame.height
}
}
extension UIViewController {
var navigationTitleColor: UIColor? {
get {
return (self.navigationController?.navigationBar.titleTextAttributes?[NSAttributedString.Key.foregroundColor] as? UIColor) ?? nil
}
set {
let textAttributes: [NSAttributedString.Key: Any]? = [NSAttributedString.Key.foregroundColor: newValue ?? UIColor.black]
self.navigationController?.navigationBar.titleTextAttributes = textAttributes
if #available(iOS 11.0, *) {
self.navigationController?.navigationBar.largeTitleTextAttributes = textAttributes
}
}
}
}
@@ -0,0 +1,298 @@
// 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
// MARK: - layout
public extension UIView {
var topSafeArea: CGFloat {
var topSafeArea: CGFloat = 0
if #available(iOS 11.0, *) {
topSafeArea = self.safeAreaInsets.top
}
return topSafeArea
}
var bottomSafeArea: CGFloat {
var bottomSafeArea: CGFloat = 0
if #available(iOS 11.0, *) {
bottomSafeArea = self.safeAreaInsets.bottom
}
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 setWidth(_ width: CGFloat) {
self.frame = CGRect.init(x: self.frame.origin.x, y: self.frame.origin.y, width: width, height: self.frame.height)
}
func setEqualsFrameFromBounds(_ view: UIView, withWidthFactor widthFactor: CGFloat = 1, maxWidth: CGFloat? = nil, withHeightFactor heightFactor: CGFloat = 1, maxHeight: CGFloat? = nil, withCentering: Bool = false) {
self.setEqualsFrameFromBounds(view.bounds, withWidthFactor: widthFactor, maxWidth: maxWidth, withHeightFactor: heightFactor, maxHeight: maxHeight, withCentering: withCentering)
}
func setEqualsFrameFromBounds(_ bounds: CGRect, withWidthFactor widthFactor: CGFloat = 1, maxWidth: CGFloat? = nil, withHeightFactor heightFactor: CGFloat = 1, maxHeight: CGFloat? = nil, withCentering: Bool = false) {
var width = bounds.width * widthFactor
if maxWidth != nil {
width.setIfMore(when: maxWidth!)
}
var height = bounds.height * heightFactor
if maxHeight != nil {
height.setIfMore(when: maxHeight!)
}
self.frame = CGRect.init(x: 0, y: 0, width: width, height: height)
if withCentering {
self.center.x = bounds.width / 2
self.center.y = bounds.height / 2
}
}
func setEqualsBoundsFromSuperview(customWidth: CGFloat? = nil, customHeight: CGFloat? = nil) {
if self.superview == nil {
return
}
self.frame = CGRect.init(origin: CGPoint.zero, size: self.superview!.frame.size)
if customWidth != nil {
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: customWidth!, height: self.frame.height))
}
if customHeight != nil {
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: self.frame.width, height: customHeight!))
}
}
func resize(newWidth width: CGFloat) {
let relativeFactor = self.frame.width / self.frame.height
if relativeFactor.isNaN {
return
}
self.frame = CGRect.init(
x: self.frame.origin.x,
y: self.frame.origin.y,
width: width,
height: width * relativeFactor
)
}
func resize(newHeight height: CGFloat) {
let relativeFactor = self.frame.width / self.frame.height
if relativeFactor.isNaN {
return
}
self.frame = CGRect.init(
x: self.frame.origin.x,
y: self.frame.origin.y,
width: height / relativeFactor,
height: height
)
}
func setXCenteringFromSuperview() {
self.center.x = (self.superview?.frame.width ?? 0) / 2
}
func setToCenterInSuperview() {
self.center = CGPoint.init(x: ((self.superview?.frame.width) ?? 0) / 2, y: ((self.superview?.frame.height) ?? 0) / 2)
}
}
public extension UIView {
func setParalax(amountFactor: CGFloat) {
let amount = self.frame.minSideSize * amountFactor
self.setParalax(amount: amount)
}
func setParalax(amount: CGFloat) {
self.motionEffects.removeAll()
let horizontal = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
horizontal.minimumRelativeValue = -amount
horizontal.maximumRelativeValue = amount
let vertical = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
vertical.minimumRelativeValue = -amount
vertical.maximumRelativeValue = amount
let group = UIMotionEffectGroup()
group.motionEffects = [horizontal, vertical]
self.addMotionEffect(group)
}
}
// MARK: - convertToImage
public extension UIView {
func convertToImage() -> UIImage {
return UIImage.drawFromView(view: self)
}
}
// MARK: - gradeView
public extension UIView {
func addGrade(alpha: CGFloat, color: UIColor = UIColor.black) -> UIView {
let gradeView = UIView.init()
gradeView.alpha = 0
self.addSubview(gradeView)
SPConstraintsAssistent.setEqualSizeConstraint(gradeView, superVuew: self)
gradeView.alpha = alpha
gradeView.backgroundColor = color
return gradeView
}
}
// MARK: - shadow
extension UIView {
func setShadow(
xTranslationFactor: CGFloat,
yTranslationFactor: CGFloat,
widthRelativeFactor: CGFloat,
heightRelativeFactor: CGFloat,
blurRadiusFactor: CGFloat,
shadowOpacity: CGFloat,
cornerRadiusFactor: CGFloat = 0
) {
let shadowWidth = self.frame.width * widthRelativeFactor
let shadowHeight = self.frame.height * heightRelativeFactor
let xTranslation = (self.frame.width - shadowWidth) / 2 + (self.frame.width * xTranslationFactor)
let yTranslation = (self.frame.height - shadowHeight) / 2 + (self.frame.height * yTranslationFactor)
let cornerRadius = self.frame.minSideSize * cornerRadiusFactor
let shadowPath = UIBezierPath.init(
roundedRect: CGRect.init(x: xTranslation, y: yTranslation, width: shadowWidth, height: shadowHeight),
cornerRadius: cornerRadius
)
let blurRadius = self.frame.minSideSize * blurRadiusFactor
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize.zero
self.layer.shadowOpacity = Float(shadowOpacity)
self.layer.shadowRadius = blurRadius
self.layer.masksToBounds = false
self.layer.shadowPath = shadowPath.cgPath;
}
func setShadow(
xTranslation: CGFloat,
yTranslation: CGFloat,
widthRelativeFactor: CGFloat,
heightRelativeFactor: CGFloat,
blurRadius: CGFloat,
shadowOpacity: CGFloat,
cornerRadius: CGFloat = 0
) {
let shadowWidth = self.frame.width * widthRelativeFactor
let shadowHeight = self.frame.height * heightRelativeFactor
let shadowPath = UIBezierPath.init(
roundedRect: CGRect.init(x: xTranslation, y: yTranslation, width: shadowWidth, height: shadowHeight),
cornerRadius: cornerRadius
)
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize.zero
self.layer.shadowOpacity = Float(shadowOpacity)
self.layer.shadowRadius = blurRadius
self.layer.masksToBounds = false
self.layer.shadowPath = shadowPath.cgPath
}
func removeShadow() {
self.layer.shadowColor = nil
self.layer.shadowOffset = CGSize.zero
self.layer.shadowOpacity = 0
self.layer.shadowPath = nil
}
func addShadowOpacityAnimation(to: CGFloat, duration: CFTimeInterval) {
let animation = CABasicAnimation(keyPath:"shadowOpacity")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
animation.fromValue = self.layer.cornerRadius
animation.fromValue = self.layer.shadowOpacity
animation.toValue = to
animation.duration = duration
self.layer.add(animation, forKey: "shadowOpacity")
self.layer.shadowOpacity = Float(to)
}
}
// MARK: - animation
extension UIView {
func addCornerRadiusAnimation(to: CGFloat, duration: CFTimeInterval) {
let animation = CABasicAnimation(keyPath:"cornerRadius")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
animation.fromValue = self.layer.cornerRadius
animation.toValue = to
animation.duration = duration
self.layer.add(animation, forKey: "cornerRadius")
self.layer.cornerRadius = to
}
func show(duration: TimeInterval = 0.3) {
self.isHidden = false
SPAnimation.animate(duration, animations: {
self.alpha = 1
})
}
func hide(duration: TimeInterval = 0.3) {
SPAnimation.animate(duration, animations: {
self.alpha = 0
}, withComplection: {
self.isHidden = true
})
}
func removeAllAnimations() {
self.layer.removeAllAnimations()
}
}
// MARK: - corner radius
extension UIView {
func roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
func round() {
self.layer.cornerRadius = self.frame.minSideSize / 2
}
}
@@ -0,0 +1,55 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension UIWindow {
static var key: UIWindow? {
return UIApplication.shared.keyWindow
}
static var topSafeArea: CGFloat {
var topSafeArea: CGFloat = 0
if let window = UIWindow.key {
if #available(iOS 11.0, *) {
topSafeArea = window.safeAreaInsets.top
}
} else {
topSafeArea = 0
}
return topSafeArea
}
static var bottomSafeArea: CGFloat {
var bottomSafeArea: CGFloat = 0
if let window = UIWindow.key {
if #available(iOS 11.0, *) {
bottomSafeArea = window.safeAreaInsets.bottom
}
} else {
bottomSafeArea = 0
}
return bottomSafeArea
}
}
@@ -0,0 +1,37 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
extension UserDefaults {
func set(stringArray array: [String], forKey key: String) {
self.set(array, forKey: key)
}
func set(boolArray array: [Bool], forKey key: String) {
self.set(array, forKey: key)
}
func boolArray(forKey defaultName: String) -> [Bool] {
return UserDefaults.standard.array(forKey: defaultName) as? [Bool] ?? []
}
}
@@ -0,0 +1,45 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import SystemConfiguration
public struct SPInternetConnection {
static var isOpen: Bool {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
}
}
var flags = SCNetworkReachabilityFlags()
if !SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) {
return false
}
let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
return (isReachable && !needsConnection)
}
private init() {}
}
@@ -0,0 +1,45 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
struct SPLaunch {
static func run() {
self.count += 1
}
static var count: Int {
get {
return UserDefaults.standard.value(forKey: "SPLaunchCount") as? Int ?? 0
}
set {
UserDefaults.standard.set(newValue, forKey: "SPLaunchCount")
}
}
static var isFirstOpen: Bool {
return (self.count == 1) || (self.count == 0)
}
private init() {}
}
@@ -0,0 +1,42 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPLayout {
static func sizeWith(widthFactor: CGFloat, maxWidth: CGFloat, heightFactor: CGFloat, maxHeight: CGFloat, relativeSideFactor: CGFloat, from relativeSize: CGSize) -> CGSize {
var widthArea = relativeSize.width * widthFactor
var heightArea = relativeSize.height * heightFactor
widthArea.setIfMore(when: maxWidth)
heightArea.setIfMore(when: maxHeight)
var prepareWidth = widthArea
var prepareHeight = widthArea / relativeSideFactor
if prepareHeight > heightArea {
prepareHeight = heightArea
prepareWidth = heightArea * relativeSideFactor
}
return CGSize.init(width: prepareWidth, height: prepareHeight)
}
}
@@ -0,0 +1,59 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
enum SPLocale: String, CaseIterable {
case ru = "ru"
case en = "en"
static var current: SPLocale {
set {
UserDefaults.standard.set([newValue.languageCode], forKey: "AppleLanguages")
}
get {
let locale = Locale.preferredLanguages[0].components(separatedBy: "-")[0]
switch locale {
case "en":
return .en
case "ru":
return .ru
default:
return .ru
}
}
}
var languageCode: String {
return self.rawValue
}
var describtion: String {
switch self {
case .en:
return "English"
case .ru:
return "Русский"
}
}
}
@@ -0,0 +1,75 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
import MessageUI
struct SPMail {
static var canSendEmail: Bool {
return MFMailComposeViewController.canSendMail()
}
static func openMailApp(to email: String, subject: String? = nil, body: String? = nil) {
let parametrs = "mailto:\(email)?subject=\(subject ?? "")&body=\(body ?? "")".addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
if parametrs != nil {
if let url: URL = URL(string: parametrs!) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
}
}
}
}
static func mailDialog(to email: String, subject: String? = nil, body: String? = nil, on viewController: UIViewController) {
let mailVC = MFMailComposeViewController()
mailVC.mailComposeDelegate = SPMailSingltone.sharedInstance
mailVC.setToRecipients([email])
if subject != nil {
mailVC.setSubject(subject!)
}
if body != nil {
mailVC.setMessageBody(body!, isHTML: false)
}
viewController.present(mailVC, animated: true, completion: nil)
}
fileprivate final class SPMailSingltone: NSObject, MFMailComposeViewControllerDelegate {
static let sharedInstance = SPMailSingltone()
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
}
private init() {}
}
// 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,86 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
import UserNotifications
struct SPLocalNotification {
static func add(from timeInterval: TimeInterval, body: String, title: String? = nil, identifier: String? = nil) {
let content = UNMutableNotificationContent()
content.body = body
content.title = title ?? ""
content.badge = NSNumber(value: SPBadge.number + 1)
content.sound = UNNotificationSound.default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false)
let identifier = identifier ?? "\(timeInterval)\(body)\(Int.random(min: 0, max: 1000))"
let notification = UNNotificationRequest(
identifier: identifier,
content: content,
trigger: trigger
)
let center = UNUserNotificationCenter.current()
center.add(notification) { (error) in
if let error = error {
print("SPLocalNotification - \(error)")
}
}
}
static func add(in date: Date, body: String, title: String? = nil, identifier: String? = nil) {
let content = UNMutableNotificationContent()
content.body = body
content.title = title ?? ""
content.badge = NSNumber(value: SPBadge.number + 1)
content.sound = UNNotificationSound.default
let triggerDate = Calendar.current.dateComponents([.year,.month,.day,.hour,.minute,.second,], from: date)
let trigger = UNCalendarNotificationTrigger.init(dateMatching: triggerDate, repeats: false)
let identifier = identifier ?? "\(date)\(body)\(Int.random(min: 0, max: 1000))"
let notification = UNNotificationRequest(
identifier: identifier,
content: content,
trigger: trigger
)
let center = UNUserNotificationCenter.current()
center.add(notification) { (error) in
if let error = error {
print("SPLocalNotification - \(error)")
}
}
}
static func remove(identifier: String) {
let center = UNUserNotificationCenter.current()
center.removePendingNotificationRequests(withIdentifiers: [identifier])
}
private init() {}
}
@@ -0,0 +1,111 @@
// 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
struct SPOpener {
struct App {
static func system(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("SPOpener - Photos opened: \(success)")
})
} else {
UIApplication.shared.openURL(settingsUrl as URL)
}
} else {
print("SPOpener - 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("SPOpener - Settings opened: \(success)")
})
} else {
UIApplication.shared.openURL(settingsUrl as URL)
}
} else {
print("SPOpener - Settings not opened")
}
}
}
}
private init() {}
}
struct Link {
public static func redirectToBrowserAndOpen(link: String) {
guard let url = URL(string: link) else {
print("SPOpener - can not create URL")
return
}
self.redirectToBrowserAndOpen(link: url)
}
public static func redirectToBrowserAndOpen(link: URL) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(link, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
} else {
UIApplication.shared.openURL(link)
}
}
public static func openInsideApp(link: String, on viewController: UIViewController) {
if let url = URL.init(string: link) {
let safariController = SFSafariViewController.init(url: url)
viewController.present(safariController, animated: true, completion: nil)
} else {
print("SPOpener - openInsideApp - invalid link")
}
}
private init() {}
}
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,409 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
import AVFoundation
import UserNotifications
import Photos
import MapKit
import EventKit
import Contacts
import Speech
import MediaPlayer
import HealthKit
public struct SPPermission {
public static func isAllow(_ permission: SPPermissionType) -> Bool {
let manager = self.getManagerForPermission(permission)
return manager.isAuthorized()
}
public static func request(_ permission: SPPermissionType, with complectionHandler: @escaping ()->()) {
let manager = self.getManagerForPermission(permission)
manager.request(withComlectionHandler: {
complectionHandler()
})
}
private init() {}
}
fileprivate protocol SPPermissionInterface {
func isAuthorized() -> Bool
func request(withComlectionHandler complectionHandler: @escaping ()->()?)
}
extension SPPermission {
fileprivate static func getManagerForPermission(_ permission: SPPermissionType) -> SPPermissionInterface {
switch permission {
case .camera:
return SPCameraPermission()
case .photoLibrary:
return SPPhotoLibraryPermission()
case .notification:
return SPNotificationPermission()
case .microphone:
return SPMicrophonePermission()
case .calendar:
return SPCalendarPermission()
case .contacts:
return SPContactsPermission()
case .reminders:
return SPRemindersPermission()
case .speech:
return SPSpeechPermission()
case .locationAlways:
return SPLocationPermission(type: SPLocationPermission.SPLocationType.Always)
case .locationWhenInUse:
return SPLocationPermission(type: SPLocationPermission.SPLocationType.WhenInUse)
case .locationWithBackground:
return SPLocationPermission(type: SPLocationPermission.SPLocationType.AlwaysWithBackground)
case .mediaLibrary:
return SPMediaLibraryPermission()
}
}
}
extension SPPermission {
fileprivate struct SPCameraPermission: SPPermissionInterface {
func isAuthorized() -> Bool {
if AVCaptureDevice.authorizationStatus(for: AVMediaType.video) == AVAuthorizationStatus.authorized {
return true
} else {
return false
}
}
func request(withComlectionHandler complectionHandler: @escaping ()->()?) {
AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: {
finished in
DispatchQueue.main.async {
complectionHandler()
}
})
}
}
fileprivate struct SPNotificationPermission: SPPermissionInterface {
func isAuthorized() -> Bool {
return UIApplication.shared.isRegisteredForRemoteNotifications
}
func request(withComlectionHandler complectionHandler: @escaping ()->()?) {
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options:[.badge, .alert, .sound]) { (granted, error) in
DispatchQueue.main.async {
complectionHandler()
}
}
} else {
UIApplication.shared.registerUserNotificationSettings(UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil))
DispatchQueue.main.async {
complectionHandler()
}
}
UIApplication.shared.registerForRemoteNotifications()
}
}
fileprivate struct SPPhotoLibraryPermission: SPPermissionInterface {
func isAuthorized() -> Bool {
if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized {
return true
} else {
return false
}
}
func request(withComlectionHandler complectionHandler: @escaping ()->()?) {
PHPhotoLibrary.requestAuthorization({
finished in
DispatchQueue.main.async {
complectionHandler()
}
})
}
}
fileprivate struct SPMicrophonePermission: SPPermissionInterface {
func isAuthorized() -> Bool {
if AVAudioSession.sharedInstance().recordPermission == .granted {
return true
}
return false
}
func request(withComlectionHandler complectionHandler: @escaping ()->()?) {
AVAudioSession.sharedInstance().requestRecordPermission {
granted in
DispatchQueue.main.async {
complectionHandler()
}
}
}
}
fileprivate struct SPCalendarPermission: SPPermissionInterface {
func isAuthorized() -> Bool {
let status = EKEventStore.authorizationStatus(for: EKEntityType.event)
switch (status) {
case EKAuthorizationStatus.authorized:
return true
default:
return false
}
}
func request(withComlectionHandler complectionHandler: @escaping ()->()?) {
let eventStore = EKEventStore()
eventStore.requestAccess(to: EKEntityType.event, completion: {
(accessGranted: Bool, error: Error?) in
DispatchQueue.main.async {
complectionHandler()
}
})
}
}
fileprivate struct SPContactsPermission: SPPermissionInterface {
func isAuthorized() -> Bool {
if #available(iOS 9.0, *) {
let status = CNContactStore.authorizationStatus(for: .contacts)
if status == .authorized {
return true
} else {
return false
}
} else {
let status = ABAddressBookGetAuthorizationStatus()
if status == .authorized {
return true
} else {
return false
}
}
}
func request(withComlectionHandler complectionHandler: @escaping ()->()?) {
if #available(iOS 9.0, *) {
let store = CNContactStore()
store.requestAccess(for: .contacts, completionHandler: { (granted, error) in
DispatchQueue.main.async {
complectionHandler()
}
})
} else {
let addressBookRef: ABAddressBook = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue()
ABAddressBookRequestAccessWithCompletion(addressBookRef) {
(granted: Bool, error: CFError?) in
DispatchQueue.main.async() {
complectionHandler()
}
}
}
}
}
fileprivate struct SPRemindersPermission: SPPermissionInterface {
func isAuthorized() -> Bool {
let status = EKEventStore.authorizationStatus(for: EKEntityType.reminder)
switch (status) {
case EKAuthorizationStatus.authorized:
return true
default:
return false
}
}
func request(withComlectionHandler complectionHandler: @escaping ()->()?) {
let eventStore = EKEventStore()
eventStore.requestAccess(to: EKEntityType.reminder, completion: {
(accessGranted: Bool, error: Error?) in
DispatchQueue.main.async {
complectionHandler()
}
})
}
}
fileprivate struct SPBluetoothPermission: SPPermissionInterface {
func isAuthorized() -> Bool {
let status = EKEventStore.authorizationStatus(for: EKEntityType.reminder)
switch (status) {
case EKAuthorizationStatus.authorized:
return true
default:
return false
}
}
func request(withComlectionHandler complectionHandler: @escaping ()->()?) {
let eventStore = EKEventStore()
eventStore.requestAccess(to: EKEntityType.reminder, completion: {
(accessGranted: Bool, error: Error?) in
DispatchQueue.main.async {
complectionHandler()
}
})
}
}
fileprivate struct SPSpeechPermission: SPPermissionInterface {
func isAuthorized() -> Bool {
guard #available(iOS 10.0, *) else { return false }
return SFSpeechRecognizer.authorizationStatus() == .authorized
}
func request(withComlectionHandler complectionHandler: @escaping ()->()?) {
guard #available(iOS 10.0, *) else {
fatalError("ios 10 or higher required")
}
SFSpeechRecognizer.requestAuthorization { status in
DispatchQueue.main.async {
complectionHandler()
}
}
}
}
fileprivate struct SPMediaLibraryPermission: SPPermissionInterface {
func isAuthorized() -> Bool {
let status = MPMediaLibrary.authorizationStatus()
if status == .authorized {
return true
} else {
return false
}
}
func request(withComlectionHandler complectionHandler: @escaping ()->()?) {
MPMediaLibrary.requestAuthorization() { status in
DispatchQueue.main.async {
complectionHandler()
}
}
}
}
fileprivate struct SPLocationPermission: SPPermissionInterface {
var type: SPLocationType
enum SPLocationType {
case Always
case WhenInUse
case AlwaysWithBackground
}
init(type: SPLocationType) {
self.type = type
}
func isAuthorized() -> Bool {
let status = CLLocationManager.authorizationStatus()
switch self.type {
case .Always:
if status == .authorizedAlways {
return true
} else {
return false
}
case .WhenInUse:
if status == .authorizedWhenInUse {
return true
} else {
return false
}
case .AlwaysWithBackground:
if status == .authorizedAlways {
return true
} else {
return false
}
}
}
func request(withComlectionHandler complectionHandler: @escaping ()->()?) {
switch self.type {
case .Always:
if SPPermissionAlwaysAuthorizationLocationHandler.shared == nil {
SPPermissionAlwaysAuthorizationLocationHandler.shared = SPPermissionAlwaysAuthorizationLocationHandler()
}
SPPermissionAlwaysAuthorizationLocationHandler.shared!.requestPermission { (authorized) in
DispatchQueue.main.async {
complectionHandler()
SPPermissionAlwaysAuthorizationLocationHandler.shared = nil
}
}
break
case .WhenInUse:
if SPPermissionWhenInUseAuthorizationLocationHandler.shared == nil {
SPPermissionWhenInUseAuthorizationLocationHandler.shared = SPPermissionWhenInUseAuthorizationLocationHandler()
}
SPPermissionWhenInUseAuthorizationLocationHandler.shared!.requestPermission { (authorized) in
DispatchQueue.main.async {
complectionHandler()
SPPermissionWhenInUseAuthorizationLocationHandler.shared = nil
}
}
break
case .AlwaysWithBackground:
if SPPermissionLocationWithBackgroundHandler.shared == nil {
SPPermissionLocationWithBackgroundHandler.shared = SPPermissionLocationWithBackgroundHandler()
}
SPPermissionLocationWithBackgroundHandler.shared!.requestPermission { (authorized) in
DispatchQueue.main.async {
complectionHandler()
SPPermissionLocationWithBackgroundHandler.shared = nil
}
}
break
}
}
}
}
@@ -0,0 +1,62 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension SPPermission {
public struct Dialog {
public static func request(with permissions: [SPPermissionType], on viewController: UIViewController, delegate: SPPermissionDialogDelegate? = nil, dataSource: SPPermissionDialogDataSource? = nil) {
if permissions.isEmpty {
return
}
let controller = SPPermissionDialogController(permissions: permissions)
controller.delegate = delegate
controller.dataSource = dataSource
controller.present(on: viewController)
}
private init() {}
}
}
@objc public protocol SPPermissionDialogDelegate: class {
@objc optional func didHide()
@objc optional func didAllow(permission: SPPermissionType)
}
@objc public protocol SPPermissionDialogDataSource: class {
@objc optional var dialogTitle: String { get }
@objc optional var dialogSubtitle: String { get }
@objc optional var dialogComment: String { get }
@objc optional var allowTitle: String { get }
@objc optional var allowedTitle: String { get }
@objc optional var bottomComment: String { get }
@objc optional var showCloseButton: Bool { get }
@objc optional func name(for permission: SPPermissionType) -> String?
@objc optional func description(for permission: SPPermissionType) -> String?
@objc optional func image(for permission: SPPermissionType) -> UIImage?
}
@@ -0,0 +1,304 @@
// 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 SPPermissionDialogController: SPBaseViewController {
weak var delegate: SPPermissionDialogDelegate?
weak var dataSource: SPPermissionDialogDataSource?
var permissions: [SPPermissionType]
var closeButton = SPSystemIconButton.init(type: .close)
var areaView = SPPermissionDialogView()
var bottomLabel = UILabel()
var backgroundView = SPGradeBlurView()
init(permissions: [SPPermissionType]) {
self.permissions = permissions
super.init(nibName: nil, bundle: nil)
}
override public func viewDidLoad() {
super.viewDidLoad()
self.backgroundView.setGradeAlpha(0, blurRaius: 0)
self.view.addSubview(self.backgroundView)
self.closeButton.backgroundColor = SPNativeStyleKit.Colors.white
self.closeButton.widthIconFactor = 0.36
self.closeButton.heightIconFactor = 0.36
self.closeButton.alpha = 0
self.closeButton.target {
self.hide(withDialog: true)
}
self.view.addSubview(self.closeButton)
self.bottomLabel.text = (self.dataSource?.bottomComment ?? "")
self.bottomLabel.font = UIFont.system(type: .Medium, size: 13)
self.bottomLabel.textColor = SPNativeStyleKit.Colors.white
self.bottomLabel.numberOfLines = 0
self.bottomLabel.setCenteringAlignment()
self.bottomLabel.alpha = 0
self.view.addSubview(self.bottomLabel)
self.areaView.subtitleLabel.text = (self.dataSource?.dialogSubtitle ?? "Permissions Request").uppercased()
self.areaView.titleLabel.text = (self.dataSource?.dialogTitle ?? "Need Permissions")
for permission in self.permissions {
let view = SPPermissionDialogLineView.init(
permission: permission,
title: (self.dataSource?.name?(for: permission) ?? permission.name),
subtitle: (self.dataSource?.description?(for: permission) ?? self.description(for: permission)),
allowTitle: self.dataSource?.allowTitle ?? "Allow",
allowedTitle: self.dataSource?.allowedTitle ?? "Allowed",
image: self.dataSource?.image?(for: permission)
)
view.button.addTarget(self, action: #selector(self.request(with:)), for: .touchUpInside)
self.areaView.add(view: view)
}
self.areaView.descriptionLabel.text = (self.dataSource?.dialogComment ?? "Permissions are necessary for the correct work of the application and the performance of all functions. Push are not required permissions")
self.view.addSubview(self.areaView)
self.areaView.layer.anchorPoint = CGPoint.init(x: 0.5, y: 0.5)
let panGesture = UIPanGestureRecognizer.init(target: self, action: #selector(self.handleGesture(sender:)))
panGesture.maximumNumberOfTouches = 1
self.areaView.addGestureRecognizer(panGesture)
self.animator = UIDynamicAnimator(referenceView: self.view)
self.updateLayout(with: self.view.frame.size)
}
@objc func request(with button: UIButton) {
var permission: SPPermissionType?
var permissionView: SPPermissionDialogLineView?
for view in self.areaView.views {
if view.button == button {
permission = view.permission
permissionView = view
break
}
}
if let permission = permission {
SPPermission.request(permission, with: {
if SPPermission.isAllow(permission) {
self.delegate?.didAllow?(permission: permission)
permissionView?.updateStyle()
for permission in self.permissions {
if SPPermission.isAllow(permission) {
if self.permissions.last == permission {
delay(0.2, closure: {
self.hide(withDialog: true)
})
}
} else {
return
}
}
}
})
}
}
func present(on viewController: UIViewController) {
self.animator.removeAllBehaviors()
self.areaView.alpha = 0
self.closeButton.alpha = 0
self.bottomLabel.alpha = 0
self.areaView.transform = .identity
self.modalPresentationStyle = .overCurrentContext
self.modalPresentationCapturesStatusBarAppearance = true
self.statusBar = (viewController.preferredStatusBarStyle == .default) ? .dark : .light
viewController.present(self, animated: false, completion: {
self.isHiddenStatusBar = true
self.areaView.center = CGPoint.init(
x: self.view.center.x,
y: self.view.center.y * 1.2
)
SPAnimation.animate(0.8, animations: {
self.backgroundView.setGradeAlpha(0.07, blurRaius: 4)
})
delay(0.21, closure: {
self.snapBehavior = UISnapBehavior(item: self.areaView, snapTo: self.areaCenter)
self.animator.addBehavior(self.snapBehavior)
SPAnimation.animate(0.3, animations: {
self.areaView.alpha = 1
})
delay(0.2, closure: {
SPAnimation.animate(0.3, animations: {
self.bottomLabel.alpha = 1
if self.dataSource?.showCloseButton ?? false {
self.closeButton.alpha = 1
}
})
})
})
})
}
func hide(withDialog: Bool) {
delay(0.2, closure: {
self.isHiddenStatusBar = false
})
if withDialog {
SPAnimationSpring.animate(
0.4,
animations: {
self.animator.removeAllBehaviors()
self.areaView.transform = CGAffineTransform.init(scaleX: 0.9, y: 0.9)
self.areaView.alpha = 0
},
options: [.curveEaseIn, .beginFromCurrentState],
withComplection: {})
}
SPAnimation.animate(0.3, animations: {
self.bottomLabel.alpha = 0
self.closeButton.alpha = 0
})
SPAnimation.animate(0.6, animations: {
self.backgroundView.setGradeAlpha(0, blurRaius: 0)
}, withComplection: {
self.dismiss(animated: false, completion: {
self.animator.removeAllBehaviors()
self.areaView.transform = .identity
self.delegate?.didHide?()
})
})
}
override func updateLayout(with size: CGSize) {
animator.removeAllBehaviors()
super.updateLayout(with: size)
self.closeButton.frame = CGRect.init(x: 0, y: 0, width: 35, height: 35)
self.closeButton.frame.bottomXPosition = size.width - 27
self.closeButton.frame.origin.y = 23
self.closeButton.round()
let shadowPath = UIBezierPath.init(
roundedRect: CGRect.init(x: 0, y: 9, width: self.closeButton.frame.width, height: self.closeButton.frame.height),
cornerRadius: self.closeButton.layer.cornerRadius
)
self.closeButton.layer.shadowColor = UIColor.black.cgColor
self.closeButton.layer.shadowOffset = CGSize.zero
self.closeButton.layer.shadowOpacity = Float(0.07)
self.closeButton.layer.shadowRadius = 17
self.closeButton.layer.masksToBounds = false
self.closeButton.layer.shadowPath = shadowPath.cgPath
self.backgroundView.frame = CGRect.init(origin: .zero, size: size)
self.areaView.layoutWidth = size.width - 20 * 2
self.areaView.layoutWidth.setIfMore(when: 380)
self.areaView.setHeight(self.areaView.layoutHeight)
self.areaView.center = self.areaCenter
var bottomLabelWidth: CGFloat = size.width * 0.4
bottomLabelWidth.setIfMore(when: 230)
self.bottomLabel.sizeToFit()
self.bottomLabel.setWidth(bottomLabelWidth)
self.bottomLabel.center.x = size.width / 2
self.bottomLabel.frame.bottomYPosition = size.height - self.bottomSafeArea - 30
self.bottomLabel.setShadowOffsetForLetters(blurRadius: 3, widthOffset: 0, heightOffset: 0, opacity: 0.18)
let bottomLabelAlpha: CGFloat = SPDevice.Orientation.isPortrait ? 1 : 0
if self.areaView.alpha == 1 {
SPAnimation.animate(0.22, animations: {
self.bottomLabel.alpha = bottomLabelAlpha
})
}
}
private var areaCenter: CGPoint {
return CGPoint(x: self.view.center.x, y: self.view.center.y)
}
//MARK: - animator
var animator = UIDynamicAnimator()
var attachmentBehavior : UIAttachmentBehavior!
var gravityBehaviour : UIGravityBehavior!
var snapBehavior : UISnapBehavior!
//MARK: - handle gesture
@objc func handleGesture(sender: AnyObject) {
let myView = self.areaView
let location = sender.location(in: view)
let boxLocation = sender.location(in: myView)
if sender.state == UIGestureRecognizer.State.began {
animator.removeAllBehaviors()
let centerOffset = UIOffset(horizontal: boxLocation.x - myView.bounds.midX, vertical: boxLocation.y - myView.bounds.midY);
attachmentBehavior = UIAttachmentBehavior(item: myView, offsetFromCenter: centerOffset, attachedToAnchor: location)
attachmentBehavior.frequency = 0
animator.addBehavior(attachmentBehavior)
}
else if sender.state == UIGestureRecognizer.State.changed {
self.attachmentBehavior.anchorPoint = location
}
else if sender.state == UIGestureRecognizer.State.ended {
self.animator.removeBehavior(attachmentBehavior)
self.snapBehavior = UISnapBehavior(item: myView, snapTo: self.areaCenter)
self.animator.addBehavior(snapBehavior)
let translation = sender.translation(in: view)
if translation.y > 100 {
animator.removeAllBehaviors()
gravityBehaviour = UIGravityBehavior(items: [areaView])
gravityBehaviour.gravityDirection = CGVector.init(dx: 0, dy: 10)
animator.addBehavior(gravityBehaviour)
self.hide(withDialog: false)
}
}
}
//MARK: - other
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPPermissionDialogController {
fileprivate func description(for permission: SPPermissionType) -> String {
switch permission {
case .camera:
return "Allow app for use camera"
case .calendar:
return "Application can add events to calendar"
case .contacts:
return "Access for your contacts and phones"
case .microphone:
return "Allow record voice from app"
case .notification:
return "Get important information without opening app."
case .photoLibrary:
return "Access for save photos in your gallery"
case .reminders:
return "Application can create new task"
case .speech:
return "Allow check you voice"
case .locationAlways:
return "App will can check your location"
case .locationWhenInUse:
return "App will can check your location"
case .locationWithBackground:
return "App will can check your location"
case .mediaLibrary:
return "Allow check your media"
}
}
}
@@ -0,0 +1,308 @@
// 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 SPPermissionDialogView: UIView {
let titleLabel = UILabel()
let subtitleLabel = UILabel()
var views: [SPPermissionDialogLineView] = []
let descriptionLabel = UILabel()
var layoutWidth: CGFloat = 0 {
didSet {
self.setWidth(self.layoutWidth)
self.layoutSubviews()
}
}
var layoutHeight: CGFloat = 0
var sideInset: CGFloat {
return 22
}
init() {
super.init(frame: .zero)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit() {
self.backgroundColor = SPNativeStyleKit.Colors.white
self.layer.cornerRadius = 17
self.subtitleLabel.text = "".uppercased()
self.subtitleLabel.numberOfLines = 0
self.subtitleLabel.textColor = SPNativeStyleKit.Colors.gray
self.subtitleLabel.font = UIFont.system(type: .DemiBold, size: 15)
self.addSubview(self.subtitleLabel)
self.titleLabel.text = ""
self.titleLabel.numberOfLines = 0
self.titleLabel.textColor = SPNativeStyleKit.Colors.black
self.titleLabel.font = UIFont.system(type: .Bold, size: 27)
self.addSubview(self.titleLabel)
self.descriptionLabel.text = ""
self.descriptionLabel.numberOfLines = 0
self.descriptionLabel.textColor = SPNativeStyleKit.Colors.gray
self.descriptionLabel.font = UIFont.system(type: .Regular, size: 11)
self.addSubview(self.descriptionLabel)
}
func add(view: SPPermissionDialogLineView) {
view.updateStyle()
self.addSubview(view)
self.views.append(view)
}
override func layoutSubviews() {
super.layoutSubviews()
self.subtitleLabel.frame = CGRect.init(x: self.sideInset, y: 0, width: self.layoutWidth - self.sideInset * 2, height: 0)
self.subtitleLabel.sizeToFit()
if SPDevice.Orientation.isPortrait {
self.subtitleLabel.frame.origin.y = 27
} else {
self.subtitleLabel.frame.origin.y = 17
}
self.titleLabel.frame = CGRect.init(x: self.sideInset, y: self.subtitleLabel.frame.bottomYPosition + 2, width: self.frame.width - self.sideInset * 2, height: 0)
self.titleLabel.sizeToFit()
var currentYPosition: CGFloat = 0
if SPDevice.Orientation.isPortrait {
currentYPosition = self.titleLabel.frame.bottomYPosition + 20
} else {
currentYPosition = self.titleLabel.frame.bottomYPosition - 2
}
for view in self.views {
view.frame = CGRect.init(x: self.sideInset, y: currentYPosition, width: self.layoutWidth - sideInset * 2, height: 10)
view.layoutSubviews()
currentYPosition += view.frame.height
}
if let view = self.views.last {
view.separatorView.isHidden = true
}
if SPDevice.Orientation.isPortrait {
self.descriptionLabel.frame = CGRect.init(x: self.sideInset, y: currentYPosition + 20, width: self.layoutWidth - self.sideInset * 2, height: 0)
self.descriptionLabel.sizeToFit()
SPAnimation.animate(0.2, animations: {
self.descriptionLabel.alpha = 1
})
self.layoutHeight = self.descriptionLabel.frame.bottomYPosition + 22
} else {
SPAnimation.animate(0.2, animations: {
self.descriptionLabel.alpha = 0
})
self.layoutHeight = currentYPosition + 2
}
let shadowPath = UIBezierPath.init(
roundedRect: CGRect.init(x: 0, y: 9, width: self.layoutWidth, height: self.layoutHeight),
cornerRadius: self.layer.cornerRadius
)
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize.zero
self.layer.shadowOpacity = Float(0.07)
self.layer.shadowRadius = 17
self.layer.masksToBounds = false
self.layer.shadowPath = shadowPath.cgPath
}
}
class SPPermissionDialogLineView: UIView {
let titleLabel = UILabel()
let subtitleLabel = UILabel()
var iconView: SPGolubevIconView = SPGolubevIconView.init()
var imageView = UIImageView()
var button = SPAppStoreActionButton()
var separatorView = UIView()
var permission: SPPermissionType
private var allowTitle: String
private var allowedTitle: String
init(permission: SPPermissionType, title: String, subtitle: String, allowTitle: String, allowedTitle: String, image: UIImage? = nil) {
self.permission = permission
self.allowTitle = allowTitle
self.allowedTitle = allowedTitle
super.init(frame: .zero)
self.titleLabel.text = title
self.subtitleLabel.text = subtitle
self.imageView.isHidden = true
if let image = image {
self.imageView.contentMode = .scaleAspectFit
self.imageView.image = image
self.iconView.isHidden = true
self.imageView.isHidden = false
} else {
switch permission {
case .calendar:
self.iconView.type = .calendar
case .camera:
self.iconView.type = .camera
case .contacts:
self.iconView.type = .book
case .microphone:
self.iconView.type = .micro
case .notification:
self.iconView.type = .ball
case .photoLibrary:
self.iconView.type = .photoLibrary
case .reminders:
self.iconView.type = .calendar
case .speech:
self.iconView.type = .micro
case .locationWhenInUse:
self.iconView.type = .compass
case .locationAlways:
self.iconView.type = .compass
case .locationWithBackground:
self.iconView.type = .compass
case .mediaLibrary:
self.iconView.type = .headphones
}
}
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
self.permission = .notification
self.allowTitle = "Allow"
self.allowedTitle = "Allowed"
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit() {
self.backgroundColor = SPNativeStyleKit.Colors.white
self.addSubview(self.imageView)
self.addSubview(self.iconView)
self.titleLabel.numberOfLines = 1
self.titleLabel.textColor = SPNativeStyleKit.Colors.black
self.titleLabel.font = UIFont.system(type: .DemiBold, size: 15)
self.addSubview(self.titleLabel)
self.subtitleLabel.numberOfLines = 2
self.subtitleLabel.textColor = SPNativeStyleKit.Colors.gray
self.subtitleLabel.font = UIFont.system(type: .Regular, size: 13)
self.addSubview(self.subtitleLabel)
self.button.setTitle(self.allowTitle)
self.button.style = .base
self.addSubview(self.button)
self.separatorView.backgroundColor = SPNativeStyleKit.Colors.gray.withAlphaComponent(0.3)
self.addSubview(self.separatorView)
}
func updateStyle() {
if SPPermission.isAllow(self.permission) {
SPAnimation.animate(0.2, animations: {
self.button.setTitle(self.allowedTitle)
self.button.style = .main
self.button.sizeToFit()
})
} else {
SPAnimation.animate(0.2, animations: {
self.button.setTitle(self.allowTitle)
self.button.style = .base
self.button.sizeToFit()
})
}
}
override func layoutSubviews() {
super.layoutSubviews()
self.setHeight(79)
self.iconView.frame = CGRect.init(x: 0, y: 0, width: 45, height: 45)
self.iconView.center.y = self.frame.height / 2
self.imageView.frame = self.iconView.frame
self.button.sizeToFit()
self.button.frame.bottomXPosition = self.frame.width
self.button.center.y = self.frame.height / 2
let titleInset: CGFloat = 15
let titlesWidth: CGFloat = self.button.frame.origin.x - self.iconView.frame.bottomXPosition - titleInset * 2
self.titleLabel.frame = CGRect.init(x: 0, y: 8, width: titlesWidth, height: 0)
self.titleLabel.sizeToFit()
self.titleLabel.setWidth(titlesWidth)
self.titleLabel.frame.origin.x = self.iconView.frame.bottomXPosition + titleInset
self.subtitleLabel.frame = CGRect.init(x: self.titleLabel.frame.origin.x + titleInset, y: 0, width: titlesWidth, height: 0)
self.subtitleLabel.sizeToFit()
self.subtitleLabel.setWidth(titlesWidth)
self.subtitleLabel.frame.origin.x = self.iconView.frame.bottomXPosition + titleInset
let allHeight = self.titleLabel.frame.height + 2 + self.subtitleLabel.frame.height
self.titleLabel.frame.origin.y = (self.frame.height - allHeight) / 2
self.subtitleLabel.frame.origin.y = self.titleLabel.frame.bottomYPosition + 2
self.separatorView.frame = CGRect.init(x: self.subtitleLabel.frame.origin.x, y: self.frame.height - 0.7, width: self.button.frame.bottomXPosition - self.subtitleLabel.frame.origin.x, height: 0.7)
self.separatorView.round()
/*self.setHeight(75)
self.iconView.frame = CGRect.init(x: 0, y: 0, width: 45, height: 45)
self.iconView.center.y = self.frame.width / 2
self.button.sizeToFit()
self.button.frame.bottomXPosition = self.frame.width
self.button.center.y = self.frame.height / 2
let titleInset: CGFloat = 15
let titlesWidth: CGFloat = self.button.frame.origin.x - self.iconView.frame.bottomXPosition - titleInset * 2
self.titleLabel.frame = CGRect.init(x: self.iconView.frame.bottomXPosition + titleInset, y: 8, width: titlesWidth, height: 0)
self.titleLabel.sizeToFit()
self.subtitleLabel.frame = CGRect.init(x: self.iconView.frame.bottomXPosition + titleInset, y: self.titleLabel.frame.bottomYPosition + 2, width: titlesWidth, height: 0)
self.subtitleLabel.setHeight(self.frame.height - 12 - self.subtitleLabel.frame.origin.y)
//self.subtitleLabel.sizeToFit()
//let height = (self.iconView.frame.bottomYPosition + 12) > (self.subtitleLabel.frame.bottomYPosition + 12) ? self.iconView.frame.bottomYPosition + 12 : self.subtitleLabel.frame.bottomYPosition + 12
//self.setHeight(75)
self.separatorView.frame = CGRect.init(x: self.subtitleLabel.frame.origin.x, y: self.frame.height - 0.7, width: self.button.frame.bottomXPosition - self.subtitleLabel.frame.origin.x, height: 0.7)
self.separatorView.round()*/
}
}
@@ -0,0 +1,158 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
import MapKit
class SPPermissionAlwaysAuthorizationLocationHandler: NSObject, CLLocationManagerDelegate {
static var shared: SPPermissionAlwaysAuthorizationLocationHandler?
lazy var locationManager: CLLocationManager = {
return CLLocationManager()
}()
var complectionHandler: SPPermissionAuthorizationHandlerCompletionBlock?
override init() {
super.init()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if whenInUseNotRealChangeStatus {
if status == .authorizedWhenInUse {
return
}
}
if status == .notDetermined {
return
}
if let complectionHandler = complectionHandler {
complectionHandler(isAuthorized())
}
}
private var whenInUseNotRealChangeStatus: Bool = false
func requestPermission(_ complectionHandler: @escaping SPPermissionAuthorizationHandlerCompletionBlock) {
self.complectionHandler = complectionHandler
let status = CLLocationManager.authorizationStatus()
switch status {
case .notDetermined:
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
break
case .authorizedWhenInUse:
self.whenInUseNotRealChangeStatus = true
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
break
default:
complectionHandler(isAuthorized())
}
}
func isAuthorized() -> Bool {
let status = CLLocationManager.authorizationStatus()
if status == .authorizedAlways {
return true
}
return false
}
deinit {
locationManager.delegate = nil
}
}
class SPPermissionWhenInUseAuthorizationLocationHandler: NSObject, CLLocationManagerDelegate {
static var shared: SPPermissionWhenInUseAuthorizationLocationHandler?
lazy var locationManager: CLLocationManager = {
return CLLocationManager()
}()
var complectionHandler: SPPermissionAuthorizationHandlerCompletionBlock?
override init() {
super.init()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .notDetermined {
return
}
if let complectionHandler = complectionHandler {
complectionHandler(isAuthorized())
}
}
func requestPermission(_ complectionHandler: @escaping SPPermissionAuthorizationHandlerCompletionBlock) {
self.complectionHandler = complectionHandler
let status = CLLocationManager.authorizationStatus()
if (status == .notDetermined) || (status == .authorizedAlways) {
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
} else {
complectionHandler(isAuthorized())
}
}
func isAuthorized() -> Bool {
let status = CLLocationManager.authorizationStatus()
if status == .authorizedWhenInUse {
return true
}
return false
}
deinit {
locationManager.delegate = nil
}
}
class SPPermissionLocationWithBackgroundHandler: SPPermissionAlwaysAuthorizationLocationHandler {
override func requestPermission(_ complectionHandler: @escaping SPPermissionAlwaysAuthorizationLocationHandler.SPPermissionAuthorizationHandlerCompletionBlock) {
if #available(iOS 9.0, *) {
locationManager.allowsBackgroundLocationUpdates = true
}
super.requestPermission(complectionHandler)
}
}
extension SPPermissionAlwaysAuthorizationLocationHandler {
typealias SPPermissionAuthorizationHandlerCompletionBlock = (Bool) -> Void
}
extension SPPermissionWhenInUseAuthorizationLocationHandler {
typealias SPPermissionAuthorizationHandlerCompletionBlock = (Bool) -> Void
}
@@ -0,0 +1,110 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public extension String {
public static func random(count: Int) -> String {
let strings = [
"В доме кардинала от меня не было тайн; не раз видел я, как он усердно перелистывает старинные книги и жадно роется в пыли фамильных рукописей. Когда я как-то упрекнул его за бесполезные бессонные ночи, после которых он впадал в болезненное уныние, он взглянул на меня с горькой улыбкой и раскрыл передо мною историю города Рима. В этой книге, в двадцатой главе жизнеописания папы Александра Шестого, я прочел следующие строки, навсегда оставшиеся в моей памяти",
"По этому поводу между отцом и сыном завязался спор. Цезарь считал, что достаточно применить одно из тех средств, которые он всегда держал наготове для своих ближайших друзей, а именно: пресловутый ключ, которым то одного, то другого просили отпереть некий шкаф. На ключе был крохотный железный шип – недосмотр слесаря. Каждый, кто трудился над тугим замком, накалывал себе палец и на другой день умирал. Был еще перстень с львиной головой, который Цезарь надевал, когда хотел пожать руку той или иной особе. Лев впивался в кожу этих избранных рук, и через сутки наступала смерть.",
"Стол накрыли в папских виноградниках возле Сан-Пьетро-ин-Винколи, в прелестном уголке, понаслышке знакомом кардиналам. Роспильози, в восторге от своего нового звания и предвкушая пир, явился с самым веселым лицом. Спада, человек осторожный и очень любивший своего племянника, молодого офицера, подававшего блистательные надежды, взял лист бумаги, перо и написал свое завещание. Потом он послал сказать племяннику, чтобы тот ждал его у виноградников; но посланный, по-видимому, не застал того дома.",
"Спада знал, что значит приглашение на обед. С тех пор как христианство – глубоко цивилизующая сила – восторжествовало в Риме, уже не центурион являлся объявить от имени тирана: «Цезарь желает, чтобы ты умер», а любезный легат с улыбкой говорил от имени папы: «Его святейшество желает, чтобы вы с ним отобедали»"
]
return String(strings.random()!.prefix(count))
}
}
public extension 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)))
}
public static func random(min: Int, max: Int) -> Int {
return Int(arc4random_uniform(UInt32(max - min - 1))) + min
}
}
public extension Double {
public static func random() -> Double {
return Double(arc4random()) / 0xFFFFFFFF
}
public static func random(min: Double, max: Double) -> Double {
return Double.random() * (max - min) + min
}
}
public extension Float {
public static func random() -> Float {
return Float(arc4random()) / 0xFFFFFFFF
}
public static func random(min: Float, max: Float) -> Float {
return Float.random() * (max - min) + min
}
}
public extension CGFloat {
public static func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
public static func random(min: CGFloat, max: CGFloat) -> CGFloat {
return CGFloat.random() * (max - min) + min
}
}
public extension Collection {
/// Return a copy of `self` with its elements shuffled
func shuffle() -> [Iterator.Element] {
var list = Array(self)
list.shuffleInPlace()
return list
}
}
extension Collection where Index == Int {
func random() -> Iterator.Element? {
return isEmpty ? nil : self[Int(arc4random_uniform(UInt32(endIndex)))]
}
}
public extension MutableCollection where Index == Int {
/// Shuffle the elements of `self` in-place.
mutating func shuffleInPlace() {
// empty and single-element collections don't shuffle
if count < 2 { return }
for i in startIndex ..< endIndex - 1 {
let j = Int(arc4random_uniform(UInt32(endIndex - i))) + i
guard i != j else { continue }
self.swapAt(i, j)
}
}
}
@@ -0,0 +1,24 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
struct SPShadow { private init() {} }
@@ -0,0 +1,98 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension SPShadow {
struct DeepStyle {
private init() {}
public static func setFor(label: UILabel) {
var offset = label.frame.height * 0.03
offset.setIfMore(when: 1)
label.setShadowOffsetForLetters(heightOffset: offset, opacity: 0.35)
}
public static func setFor(view: UIView) {
let xTranslationFactor: CGFloat = 0
let yTranslationFactor: CGFloat = 0.18
var widthRelativeFactor: CGFloat = 0.68
let heightRelativeFactor: CGFloat = 0.78
let blurRadiusFactor: CGFloat = 0.1
let shadowOpacity: CGFloat = 0.3
if view.frame.width < 120 {
widthRelativeFactor = widthRelativeFactor * 0.8
}
var shadowWidth = view.frame.width * widthRelativeFactor
if (view.frame.width - shadowWidth) / 2 > 35 {
shadowWidth = view.frame.width - 70
widthRelativeFactor = shadowWidth / view.frame.width
}
let shadowHeight = view.frame.height * heightRelativeFactor
let xTranslation = (view.frame.width - shadowWidth) / 2 + (view.frame.width * xTranslationFactor)
var yTranslation = (view.frame.height - shadowHeight) / 2 + (view.frame.height * yTranslationFactor)
let minBottomSpace: CGFloat = 6
if (yTranslation + shadowHeight - view.frame.height) < minBottomSpace {
yTranslation = view.frame.height + minBottomSpace - shadowHeight
}
let maxBottomSpace: CGFloat = 12
if (yTranslation + shadowHeight - view.frame.height) > maxBottomSpace {
yTranslation = view.frame.height + maxBottomSpace - shadowHeight
}
var blurRadius = view.frame.minSideSize * blurRadiusFactor
blurRadius.setIfMore(when: 10)
blurRadius.setIfFewer(when: 7)
view.setShadow(
xTranslation: xTranslation,
yTranslation: yTranslation,
widthRelativeFactor: widthRelativeFactor,
heightRelativeFactor: heightRelativeFactor,
blurRadius: blurRadius,
shadowOpacity: shadowOpacity,
cornerRadius: view.layer.cornerRadius
)
}
}
}
extension UIView {
func setDeepShadow() {
SPShadow.DeepStyle.setFor(view: self)
}
}
extension UILabel {
func setDeepShadowForLetters() {
SPShadow.DeepStyle.setFor(label: self)
}
}
@@ -0,0 +1,66 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public struct SPShare {
public struct Native {
static func share(text: String? = nil, fileNames: [String] = [], images: [UIImage] = [], complection: ((_ isSharing: Bool)->())? = nil, sourceView: UIView, on viewController: UIViewController) {
var shareData: [Any] = []
if text != nil {
shareData.append(text!)
}
for file in fileNames {
let path = Bundle.main.path(forResource: file, ofType: "")
if path != nil {
let fileData = URL.init(fileURLWithPath: path!)
shareData.append(fileData)
}
}
for image in images {
shareData.append(image)
}
let shareViewController = UIActivityViewController(activityItems: shareData, applicationActivities: nil)
shareViewController.completionWithItemsHandler = {(activityType: UIActivity.ActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) in
if !completed {
complection?(false)
return
}
complection?(true)
}
shareViewController.modalPresentationStyle = .popover
shareViewController.popoverPresentationController?.sourceView = sourceView
shareViewController.popoverPresentationController?.sourceRect = sourceView.bounds
viewController.present(shareViewController, animated: true, completion: nil)
}
private init() {}
}
private init() {}
}
@@ -0,0 +1,62 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPInstagram {
static var isSetApp: Bool {
if UIApplication.shared.canOpenURL(URL(string: "instagram://user?username=test")!) {
return true
} else {
return false
}
}
static func openPost(id: String) {
let instagramHooks = "instagram://media?id=\(id)"
let instagramUrl = URL(string: instagramHooks)
let safariURL = URL(string: "instagram.com/\(id)")!
if UIApplication.shared.canOpenURL(instagramUrl!) {
UIApplication.shared.open(instagramUrl!, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
} else {
SPOpener.Link.redirectToBrowserAndOpen(link: safariURL)
}
}
static func openUser(username: String) {
let instagramHooks = "instagram://user?username=\(username)"
let instagramUrl = URL(string: instagramHooks)
let safariURL = URL(string: "https://instagram.com/\(username)")!
if UIApplication.shared.canOpenURL(instagramUrl!) {
UIApplication.shared.open(instagramUrl!, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
} else {
SPOpener.Link.redirectToBrowserAndOpen(link: safariURL)
}
}
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,68 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPTelegram {
static var isSetApp: Bool {
if UIApplication.shared.canOpenURL(URL(string: "tg://msg?text=test")!) {
return true
} else {
return false
}
}
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
let urlStringEncoded = text.addingPercentEncoding( withAllowedCharacters: .urlHostAllowed)
let urlOptional = URL(string: "tg://msg?text=\(urlStringEncoded ?? "")")
if let url = urlOptional {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: complection)
} else {
complection(false)
}
} else {
complection(false)
}
}
static func joinChannel(id: String) {
let url = "https://t.me/joinchat/\(id)"
SPOpener.Link.redirectToBrowserAndOpen(link: url)
}
static func openBot(username: String) {
var username = username
if username.first == "@" {
username.removeFirst()
}
let url = "https://telegram.me/\(username)"
SPOpener.Link.redirectToBrowserAndOpen(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)})
}
@@ -0,0 +1,54 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPTwitter {
static var isSetApp: Bool {
if UIApplication.shared.canOpenURL(URL(string: "twitter://post?message=test")!) {
return true
} else {
return false
}
}
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
let urlStringEncoded = text.addingPercentEncoding( withAllowedCharacters: .urlHostAllowed)
let urlOptional = URL(string: "twitter://post?message=\(urlStringEncoded ?? "")")
if let url = urlOptional {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: complection)
} else {
complection(false)
}
} else {
complection(false)
}
}
private init() {}
}
// 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,56 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPViber {
static var isSetApp: Bool {
if UIApplication.shared.canOpenURL(URL(string: "viber://forward?text=test")!) {
return true
} else {
return false
}
}
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
let urlStringEncoded = text.addingPercentEncoding( withAllowedCharacters: .urlHostAllowed)
let urlOptional = URL(string: "viber://forward?text=\(urlStringEncoded ?? "")")
if let url = urlOptional {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: complection)
} else {
complection(false)
}
} else {
complection(false)
}
}
private init() {}
}
// 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,54 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPWhatsApp {
static var isSetApp: Bool {
if UIApplication.shared.canOpenURL(URL(string: "whatsapp://send?text=test")!) {
return true
} else {
return false
}
}
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
let urlStringEncoded = text.addingPercentEncoding( withAllowedCharacters: .urlHostAllowed)
let urlOptional = URL(string: "whatsapp://send?text=\(urlStringEncoded ?? "")")
if let url = urlOptional {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: complection)
} else {
complection(false)
}
} else {
complection(false)
}
}
private init() {}
}
// 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,44 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public struct SPNativeStyleKit {
struct Colors {
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,27 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public struct SPStyleKit {
private init() {}
}
@@ -0,0 +1,118 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public enum SPStatusBar {
case dark
case light
}
public enum SPSnapToSide {
case left
case right
}
public enum SPSystemIconType {
case share
case close
case favorite
case favorite_fill
}
public enum SPSocialNetwork {
case whatsapp
case telegram
case vk
case facebook
case viber
}
public enum SPOauthState {
case succsess
case unvalidLogin
case invalidLogin
case unvalidPassword
case invalidPassword
case needTwoFactor
case error
}
public enum SPSeparatorInsetStyle {
case beforeImage
case all
case none
case auto
}
@objc public enum SPPermissionType: Int {
case camera = 0
case photoLibrary = 1
case notification = 2
case microphone = 3
case calendar = 4
case contacts = 5
case reminders = 6
case speech = 7
case locationAlways = 8
case locationWhenInUse = 9
case locationWithBackground = 10
case mediaLibrary = 11
var name: String {
switch self {
case .camera:
return "Camera"
case .photoLibrary:
return "Photo Library"
case .notification:
return "Notification"
case .microphone:
return "Microphone"
case .calendar:
return "Calendar"
case .contacts:
return "Contacts"
case .reminders:
return "Reminders"
case .speech:
return "Speech"
case .locationAlways:
return "Location"
case .locationWhenInUse:
return "Location"
case .locationWithBackground:
return "Location"
case .mediaLibrary:
return "Media Library"
}
}
}
public enum SPNavigationTitleStyle {
case large
case small
}
public enum SPSystemApp {
case photos
case setting
}
@@ -0,0 +1,127 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPAppStoreActionButton: SPDownloadingButton {
var style: Style = .base {
didSet {
self.setTitleColorForNoramlAndHightlightedStates(color: self.baseColor)
self.setTitle(self.titleLabel?.text, for: UIControl.State.normal)
switch self.style {
case .base:
self.backgroundColor = self.secondColor
self.titleLabel?.font = UIFont.system(type: .Bold, size: 14)
self.contentEdgeInsets = UIEdgeInsets.init(top: 6, left: 15, bottom: 6, right: 15)
break
case .main:
self.backgroundColor = self.baseColor
self.layer.borderWidth = 0
self.setTitleColorForNoramlAndHightlightedStates(color: UIColor.white)
self.titleLabel?.font = UIFont.system(type: .Bold, size: 14)
self.contentEdgeInsets = UIEdgeInsets.init(top: 6, left: 15, bottom: 6, right: 15)
break
case .buyInStore:
self.backgroundColor = self.baseColor
self.layer.borderWidth = 0
self.setTitleColorForNoramlAndHightlightedStates(color: UIColor.white)
self.titleLabel?.font = UIFont.system(type: .Bold, size: 14)
self.contentEdgeInsets = UIEdgeInsets.init(top: 8, left: 15, bottom: 8, right: 15)
break
case .line:
self.backgroundColor = UIColor.clear
self.layer.borderWidth = 1
self.layer.borderColor = self.baseColor.cgColor
self.titleLabel?.font = UIFont.system(type: .Medium, size: 14)
self.contentEdgeInsets = UIEdgeInsets.init(top: 6, left: 15, bottom: 6, right: 15)
break
}
}
}
var baseColor: UIColor = UIColor.init(hex: "0076FF") {
didSet {
let currentStyle = self.style
self.style = currentStyle
}
}
var secondColor: UIColor = UIColor.init(hex: "F0F1F6") {
didSet {
let currentStyle = self.style
self.style = currentStyle
}
}
init() {
super.init(frame: CGRect.zero)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit() {
self.style = .base
self.layer.masksToBounds = true
}
override func setTitle(_ title: String?, for state: UIControl.State) {
switch self.style {
case .base:
super.setTitle(title?.uppercased(), for: state)
case .main:
super.setTitle(title?.uppercased(), for: state)
case .buyInStore:
super.setTitle(title?.uppercasedFirstLetter(), for: state)
case .line:
super.setTitle(title?.uppercased(), for: state)
}
}
override func layoutSubviews() {
super.layoutSubviews()
if self.style == .buyInStore {
self.layer.cornerRadius = 12
} else {
self.round()
}
if self.style != .main {
if let count = self.titleLabel?.text?.count {
if count < 4 {
self.contentEdgeInsets = UIEdgeInsets.init(top: 6, left: 22, bottom: 6, right: 22)
}
}
}
}
enum Style {
case base
case main
case buyInStore
case line
}
}
@@ -0,0 +1,62 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPDownloadingButton: UIButton {
let activityIndicatorView = UIActivityIndicatorView.init()
var isFrameRounded: Bool = false
func startLoading() {
self.activityIndicatorView.alpha = 0
self.activityIndicatorView.isHidden = false
self.activityIndicatorView.startAnimating()
self.activityIndicatorView.color = self.titleLabel?.textColor
self.addSubview(self.activityIndicatorView)
self.hideContent(completion: {
SPAnimation.animate(0.2, animations: {
self.activityIndicatorView.alpha = 1
})
})
}
func stopLoading() {
SPAnimation.animate(0.2, animations: {
self.activityIndicatorView.alpha = 0
}, withComplection: {
self.activityIndicatorView.removeFromSuperview()
self.activityIndicatorView.isHidden = true
self.activityIndicatorView.stopAnimating()
self.showContent()
})
}
override func layoutSubviews() {
super.layoutSubviews()
self.activityIndicatorView.center = CGPoint.init(x: self.frame.width / 2, y: self.frame.height / 2)
if self.isFrameRounded {
self.round()
}
}
}
@@ -0,0 +1,48 @@
// 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 SPGradientButton: SPDownloadingButton {
let gradientView = SPGradientView.init()
init() {
super.init(frame: CGRect.zero)
self.gradientView.setStartColorPosition(SPGradientView.Position.MediumLeft)
self.gradientView.setEndColorPosition(SPGradientView.Position.MediumRight)
self.gradientView.startColor = UIColor.init(hex: "5737F6")
self.gradientView.endColor = UIColor.init(hex: "956BFE")
self.gradientView.isUserInteractionEnabled = false
self.layer.masksToBounds = true
self.addSubview(self.gradientView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.gradientView.setEqualsBoundsFromSuperview()
}
}
@@ -0,0 +1,55 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPMengButton: SPGradientButton {
override init() {
super.init()
self.isFrameRounded = true
self.contentEdgeInsets = UIEdgeInsets.init(top: 12, left: 21, bottom: 12, right: 21)
self.titleLabel?.font = UIFont.system(type: UIFont.BoldType.Bold, size: 17)
self.setTitleColorForNoramlAndHightlightedStates(color: UIColor.white)
self.layer.masksToBounds = true
self.gradientView.layer.masksToBounds = true
self.gradientView.setStartColorPosition(SPGradientView.Position.MediumLeft)
self.gradientView.setEndColorPosition(SPGradientView.Position.MediumRight)
self.gradientView.startColor = UIColor.init(hex: "5737F6")
self.gradientView.endColor = UIColor.init(hex: "956BFE")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
if self.isFrameRounded {
self.gradientView.round()
self.round()
}
}
}
@@ -0,0 +1,65 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPNativeOS11Button: SPDownloadingButton {
override var isHighlighted: Bool {
didSet {
if isHighlighted {
self.backgroundColor = self.backgroundColor?.withAlphaComponent(0.7)
} else {
self.backgroundColor = self.backgroundColor?.withAlphaComponent(1)
}
}
}
init() {
super.init(frame: CGRect.zero)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit() {
self.titleLabel?.font = UIFont.system(type: UIFont.BoldType.DemiBold, size: 16)
self.setTitleColorForNoramlAndHightlightedStates(color: UIColor.white)
self.backgroundColor = SPNativeStyleKit.Colors.blue
self.layer.masksToBounds = true
self.layer.cornerRadius = 8
self.contentEdgeInsets = UIEdgeInsets.init(top: 15, left: 15, bottom: 15, right: 15)
}
override func sizeToFit() {
super.sizeToFit()
if let superview = self.superview {
let sideSpace: CGFloat = superview.frame.width * 0.112
var width = superview.frame.width - sideSpace * 2
width.setIfMore(when: 335)
self.setWidth(width)
}
}
}
@@ -0,0 +1,77 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPPlayCircleButton: UIButton {
var audioState: AudioState = AudioState.play {
didSet {
switch self.audioState {
case .play:
self.iconView.type = .play
break
case .pause:
self.iconView.type = .pause
break
case .stop:
self.iconView.type = .stop
break
}
}
}
var iconColor = SPNativeStyleKit.Colors.white {
didSet {
self.iconView.color = self.iconColor
}
}
let iconView = SPAudioIconView.init()
init() {
super.init(frame: CGRect.zero)
self.addSubview(self.iconView)
self.iconView.isUserInteractionEnabled = false
self.setTitle("", for: .normal)
self.backgroundColor = SPNativeStyleKit.Colors.blue
self.audioState = .play
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.iconView.setEqualsFrameFromBounds(self, withWidthFactor: 0.45, withHeightFactor: 0.45, withCentering: true)
self.round()
}
enum AudioState {
case play
case pause
case stop
}
}
@@ -0,0 +1,75 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPRoundButton: SPRoundFrameButton {
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit() {
self.backgroundColor = UIColor.white
self.layer.borderWidth = 0
}
}
public class SPRoundLineButton: SPRoundFrameButton {
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit() {
self.backgroundColor = UIColor.clear
self.layer.borderWidth = 2
self.layer.borderColor = UIColor(hue: 0,
saturation: 0,
brightness: 100,
alpha: 0.5).cgColor
self.layer.borderColor = UIColor.init(white: 1, alpha: 0.5).cgColor
}
}
public class SPRoundFrameButton: UIButton {
override public func layoutSubviews() {
super.layoutSubviews()
let minSide = min(self.frame.width, self.frame.height)
self.layer.cornerRadius = minSide / 2
self.clipsToBounds = true
}
}
@@ -0,0 +1,88 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPSocialIconButton: UIButton {
let iconView = SPSocialIconView.init()
var widthIconFactor: CGFloat = 0.5
var heightIconFactor: CGFloat = 0.5
var type: SPSocialNetwork {
didSet {
self.iconView.type = self.type
}
}
override var isHighlighted: Bool {
didSet {
if isHighlighted {
self.iconView.color = self.iconView.color.withAlphaComponent(0.7)
} else {
self.iconView.color = self.iconView.color.withAlphaComponent(1)
}
}
}
override var isEnabled: Bool {
didSet {
if isEnabled {
self.alpha = 1
} else {
self.alpha = 0.5
}
}
}
init() {
self.type = .facebook
super.init(frame: CGRect.zero)
self.commonInit()
}
init(type: SPSocialNetwork) {
self.type = type
super.init(frame: CGRect.zero)
self.commonInit()
defer {
self.type = type
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func commonInit() {
self.iconView.isUserInteractionEnabled = false
self.addSubview(self.iconView)
self.backgroundColor = SPNativeStyleKit.Colors.blue
self.iconView.color = SPNativeStyleKit.Colors.white
}
override func layoutSubviews() {
super.layoutSubviews()
self.iconView.setEqualsFrameFromBounds(self, withWidthFactor: self.widthIconFactor, withHeightFactor: self.heightIconFactor, withCentering: true)
self.round()
}
}
@@ -0,0 +1,80 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPSystemIconButton: UIButton {
let iconView = SPSystemIconView.init()
var widthIconFactor: CGFloat = 1
var heightIconFactor: CGFloat = 1
var type: SPSystemIconType {
didSet {
self.iconView.type = self.type
}
}
var color = SPNativeStyleKit.Colors.blue {
didSet {
self.iconView.color = self.color
}
}
override var isHighlighted: Bool {
didSet {
if isHighlighted {
self.iconView.color = self.color.withAlphaComponent(0.7)
} else {
self.iconView.color = self.color.withAlphaComponent(1)
}
}
}
init() {
self.type = .share
super.init(frame: CGRect.zero)
self.commonInit()
}
init(type: SPSystemIconType) {
self.type = type
super.init(frame: CGRect.zero)
self.iconView.type = self.type
self.type = type
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func commonInit() {
self.iconView.isUserInteractionEnabled = false
self.addSubview(self.iconView)
}
override func layoutSubviews() {
super.layoutSubviews()
self.iconView.setEqualsFrameFromBounds(self, withWidthFactor: self.widthIconFactor, withHeightFactor: self.heightIconFactor, withCentering: true)
}
}
@@ -0,0 +1,302 @@
// 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 SPBaseTableViewController: SPStatusBarManagerTableViewController {
var activityIndicatorView = UIActivityIndicatorView.init()
var searchController: UISearchController?
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(notification:)), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidHide(notification:)), name: UIResponder.keyboardDidHideNotification, object: nil)
self.activityIndicatorView.stopAnimating()
self.activityIndicatorView.color = SPNativeStyleKit.Colors.gray
self.view.addSubview(self.activityIndicatorView)
self.updateLayout(with: self.view.frame.size)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (contex) in
self.updateLayout(with: size)
}, completion: nil)
}
@available(iOS 11.0, *)
override func viewLayoutMarginsDidChange() {
super.viewLayoutMarginsDidChange()
self.updateLayout(with: self.view.frame.size)
}
func updateLayout(with size: CGSize) {
let layoutIfShowKeyboard = {
let height = size.height - (self.keyboardSize?.height ?? 0) - self.topSafeArea
self.emptyProposeView?.frame = CGRect.init(
x: 0, y: 0,
width: size.width * self.emptyProposeViewWidthFactor,
height: height
)
self.emptyProposeView?.center.x = size.width / 2
}
let layoutIfNotShowKeyboard = {
let height = size.height - self.topSafeArea - self.bottomSafeArea
self.emptyProposeView?.frame = CGRect.init(
x: 0, y: 0,
width: size.width * self.emptyProposeViewWidthFactor,
height: height
)
self.emptyProposeView?.center.x = size.width / 2
}
if self.isShowKeyboard {
if self.isAllowLayoutWithKeyboardEvents {
layoutIfShowKeyboard()
} else {
layoutIfNotShowKeyboard()
}
} else {
layoutIfNotShowKeyboard()
}
if let center = self.emptyProposeView?.center {
self.activityIndicatorView.center = center
} else {
self.activityIndicatorView.center = CGPoint.init(
x: size.width / 2,
y: (size.height - self.topSafeArea - self.bottomSafeArea) / 2
)
}
}
//MARK: - Keyboard
var isShowKeyboard: Bool = false
var isAllowLayoutWithKeyboardEvents: Bool = false
var keyboardSize: CGSize? = nil
func keyboardWillShow(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
func keyboardDidShow(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
func keyboardWillHide(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
func keyboardDidHide(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
@objc func keyboardWillShow(notification: NSNotification) {
self.isShowKeyboard = true
self.keyboardSize = self.keyboardSize(from: notification)
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardWillShow(duration: duration)
}
@objc func keyboardDidShow(notification: NSNotification) {
self.isShowKeyboard = true
self.keyboardSize = self.keyboardSize(from: notification)
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardDidShow(duration: duration)
}
@objc func keyboardWillHide(notification: NSNotification) {
self.isShowKeyboard = false
self.keyboardSize = nil
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardWillHide(duration: duration)
}
@objc func keyboardDidHide(notification: NSNotification) {
self.isShowKeyboard = false
self.keyboardSize = nil
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardDidHide(duration: duration)
}
func keyboardSize(from notification: NSNotification) -> CGSize? {
let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
return keyboardFrame?.cgRectValue.size
}
func keyboardAnimateDuration(from notification: NSNotification) -> TimeInterval? {
return notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval
}
//MARK: - Empty Propose View
var emptyProposeViewYTranslateFactor: CGFloat = 0.98
var emptyProposeViewWidthFactor: CGFloat = 0.7
private var emptyProposeView: UIView? {
didSet {
if self.emptyProposeView != nil {
self.view.addSubview(self.emptyProposeView!)
self.updateLayout(with: self.view.frame.size)
}
}
}
func setEmptyProposeView(_ view: UIView) {
self.emptyProposeView = view
self.hideEmptyProposeView(animated: false)
}
func hideEmptyProposeView(animated: Bool) {
let hideFunc = {
self.emptyProposeView?.isHidden = true
self.emptyProposeView?.alpha = 0
}
if animated {
SPAnimation.animate(0.3, animations: {
self.emptyProposeView?.alpha = 0
}, withComplection: {
hideFunc()
})
} else {
hideFunc()
}
}
func showEmptyProposeView(animated: Bool) {
self.emptyProposeView?.isHidden = false
if animated {
SPAnimation.animate(0.3, animations: {
self.emptyProposeView?.alpha = 1
})
} else {
self.emptyProposeView?.alpha = 1
}
}
//MARK: - Cache
private var cacheImages: [(link: String, image: UIImage)] = []
func toCache(link: String, image: UIImage?) {
if image == nil {
return
}
if self.fromCahce(link: link) == nil {
self.cacheImages.append((link: link, image: image!))
}
}
func fromCahce(link: String) -> UIImage? {
let cachedData = self.cacheImages.first(where: {
$0.link == link
})
return cachedData?.image
}
//MARK: - Reload Table View
func reloadTableView(animated: Bool, complection: @escaping ()->() = {}) {
if animated {
UIView.transition(
with: self.tableView,
duration: 0.3,
options: [.transitionCrossDissolve, UIView.AnimationOptions.beginFromCurrentState],
animations: {
self.tableView.reloadData()
}, completion: {(state) in
complection()
})
} else {
self.tableView.reloadData()
complection()
}
}
func startLoading() {
if self.tableView.isEmpty {
self.activityIndicatorView.startAnimating()
if self.refreshControl == nil {
self.tableView.isScrollEnabled = false
}
}
self.hideEmptyProposeView(animated: false)
}
func endLoading() {
self.activityIndicatorView.stopAnimating()
if self.tableView.isEmpty {
self.reloadTableView(animated: true)
} else {
self.reloadTableView(animated: false)
}
if self.tableView.isEmpty {
if self.refreshControl == nil {
self.tableView.isScrollEnabled = false
}
self.showEmptyProposeView(animated: true)
} else {
self.tableView.isScrollEnabled = true
self.hideEmptyProposeView(animated: true)
}
if self.refreshControl?.isRefreshing ?? false {
delay(0.12, closure: {
self.refreshControl?.endRefreshing()
})
}
}
//MARK: - Search
@available(iOS 11.0, *)
func addSearchController(placeholder: String, resultController: UIViewController?, searchResultsUpdater: UISearchResultsUpdating?, searchBarDelegate: UISearchBarDelegate?) {
self.searchController = UISearchController(searchResultsController: resultController)
self.searchController?.searchBar.placeholder = placeholder
self.searchController?.searchResultsUpdater = searchResultsUpdater
self.searchController?.searchBar.delegate = searchBarDelegate
navigationItem.searchController = searchController
definesPresentationContext = true
self.searchController?.searchBar.tintColor = UINavigationController.elementsColor
}
//MARK: - Other
@objc func dismiss(sender: Any) {
self.dismiss()
}
}
@@ -0,0 +1,232 @@
// 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 SPBaseViewController: SPStatusBarManagerViewController {
var activityIndicatorLayoutWithSafeArea: Bool = true
let activityIndicatorView = UIActivityIndicatorView()
override public func viewDidLoad() {
super.viewDidLoad()
self.activityIndicatorView.style = .white
self.activityIndicatorView.stopAnimating()
self.view.addSubview(self.activityIndicatorView)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(notification:)), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidHide(notification:)), name: UIResponder.keyboardDidHideNotification, object: nil)
self.updateLayout(with: self.view.frame.size)
}
override public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (contex) in
self.updateLayout(with: size)
}, completion: nil)
}
@available(iOS 11.0, *)
override public func viewLayoutMarginsDidChange() {
super.viewLayoutMarginsDidChange()
self.updateLayout(with: self.view.frame.size)
}
func updateLayout(with size: CGSize) {
var contentHeight = size.height - self.topSafeArea
if self.isShowKeyboard {
contentHeight = contentHeight - (self.keyboardSize?.height ?? 0)
} else {
contentHeight = contentHeight - self.bottomSafeArea
}
let centerYPosition = self.topSafeArea + (contentHeight / 2)
if self.activityIndicatorLayoutWithSafeArea {
self.activityIndicatorView.center = CGPoint.init(
x: size.width / 2,
y: centerYPosition
)
} else {
self.activityIndicatorView.center = CGPoint.init(
x: size.width / 2,
y: (size.height - (self.keyboardSize?.height ?? 0)) / 2
)
}
var emptyProposeViewWidth = size.width * self.emptyProposeViewWidthFactor
if emptyProposeViewWidth < (375 * self.emptyProposeViewWidthFactor) {
emptyProposeViewWidth = size.width * 0.9
}
self.emptyProposeView?.frame = CGRect.init(
x: 0, y: 0,
width: emptyProposeViewWidth,
height: (size.height - self.topSafeArea - self.bottomSafeArea) * self.emptyProposeViewHeightFactor
)
self.emptyProposeView?.center = CGPoint.init(
x: size.width / 2,
y: centerYPosition * self.emptyProposeViewCenterYFactor
)
if self.isShowKeyboard {
self.emptyProposeView?.center = CGPoint.init(
x: size.width / 2,
y: centerYPosition
)
}
}
//MARK: - keyboard
var isShowKeyboard: Bool = false
var keyboardSize: CGSize? = nil
func keyboardWillShow(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
func keyboardDidShow(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
func keyboardWillHide(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
func keyboardDidHide(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
@objc func keyboardWillShow(notification: NSNotification) {
self.isShowKeyboard = true
self.keyboardSize = self.keyboardSize(from: notification)
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardWillShow(duration: duration)
}
@objc func keyboardDidShow(notification: NSNotification) {
self.isShowKeyboard = true
self.keyboardSize = self.keyboardSize(from: notification)
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardDidShow(duration: duration)
}
@objc func keyboardWillHide(notification: NSNotification) {
self.isShowKeyboard = false
self.keyboardSize = nil
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardWillHide(duration: duration)
}
@objc func keyboardDidHide(notification: NSNotification) {
self.isShowKeyboard = false
self.keyboardSize = nil
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardDidHide(duration: duration)
}
func keyboardSize(from notification: NSNotification) -> CGSize? {
let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
return keyboardFrame?.cgRectValue.size
}
func keyboardAnimateDuration(from notification: NSNotification) -> TimeInterval? {
return notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval
}
//MARK: - Search
@available(iOS 11.0, *)
func addSearchController(placeholder: String, resultController: UIViewController?, searchResultsUpdater: UISearchResultsUpdating?, searchBarDelegate: UISearchBarDelegate?) -> UISearchController {
let searchController = UISearchController(searchResultsController: resultController)
searchController.searchBar.placeholder = placeholder
searchController.searchResultsUpdater = searchResultsUpdater
searchController.searchBar.delegate = searchBarDelegate
navigationItem.hidesSearchBarWhenScrolling = false
navigationItem.searchController = searchController
definesPresentationContext = true
return searchController
}
//MARK: - Empty propose view
var emptyProposeViewCenterYFactor: CGFloat = 0.94
var emptyProposeViewWidthFactor: CGFloat = 0.7
var emptyProposeViewHeightFactor: CGFloat = 1
private var emptyProposeView: UIView? {
didSet {
if self.emptyProposeView != nil {
self.view.addSubview(self.emptyProposeView!)
self.updateLayout(with: self.view.frame.size)
}
}
}
func setEmptyProposeView(_ view: UIView) {
self.emptyProposeView = view
}
func hideEmptyProposeView(animated: Bool) {
let hideFunc = {
self.emptyProposeView?.isHidden = true
self.emptyProposeView?.alpha = 0
}
if animated {
SPAnimation.animate(0.3, animations: {
self.emptyProposeView?.alpha = 0
}, withComplection: {
hideFunc()
})
} else {
hideFunc()
}
}
func showEmptyProposeView(animated: Bool) {
self.emptyProposeView?.isHidden = false
if animated {
SPAnimation.animate(0.3, animations: {
self.emptyProposeView?.alpha = 1
})
} else {
self.emptyProposeView?.alpha = 1
}
}
}
@@ -0,0 +1,420 @@
// 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 SPConfirmActionViewController: UIViewController {
let navigationBarView = SPConfirmActionNavigationBar.init()
let cellsView = SPConfirmActionCellsView.init()
let confirmActionView = SPConfirmActionButtonView.init()
var confirmedAction: (_ controller: SPConfirmActionViewController)->() = { controller in
controller.hide()
}
private var animationDuration: TimeInterval {
return 0.5
}
override func viewDidLoad() {
super.viewDidLoad()
self.modalPresentationStyle = .overCurrentContext
self.modalTransitionStyle = .crossDissolve
self.view.backgroundColor = UIColor.black.withAlphaComponent(0)
self.confirmActionView.button.addTarget(self, action: #selector(self.tapConfirmButton), for: .touchUpInside)
self.navigationBarView.button.addTarget(self, action: #selector(self.hide), for: .touchUpInside)
self.view.addSubview(self.navigationBarView)
self.view.addSubview(self.cellsView)
self.view.addSubview(self.confirmActionView)
self.updateLayout(size: self.view.frame.size)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
self.updateLayout(size: size)
}
override func viewSafeAreaInsetsDidChange() {
if #available(iOS 11.0, *) {
super.viewSafeAreaInsetsDidChange()
self.updateLayout(size: self.view.frame.size)
}
}
private func updateLayout(size: CGSize) {
self.confirmActionView.setWidth(size.width)
self.confirmActionView.sizeToFit()
self.confirmActionView.frame.origin.x = 0
self.confirmActionView.frame.origin.y = size.height - self.confirmActionView.frame.height
self.cellsView.setWidth(size.width)
self.cellsView.sizeToFit()
self.cellsView.frame.origin.x = 0
self.cellsView.frame.origin.y = self.confirmActionView.frame.origin.y - self.cellsView.frame.height
self.navigationBarView.setWidth(size.width)
self.navigationBarView.sizeToFit()
self.navigationBarView.frame.origin.x = 0
self.navigationBarView.frame.origin.y = self.cellsView.frame.origin.y - self.navigationBarView.frame.height
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.confirmActionView.frame.origin.y = self.view.frame.size.height
self.cellsView.frame.origin.y = self.view.frame.size.height
self.navigationBarView.frame.origin.y = self.view.frame.size.height
SPAnimationSpring.animate(self.animationDuration, animations: {
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.4)
self.confirmActionView.frame.origin.y = self.view.frame.height - self.confirmActionView.frame.height
self.cellsView.frame.origin.y = self.confirmActionView.frame.origin.y - self.cellsView.frame.height
self.navigationBarView.frame.origin.y = self.cellsView.frame.origin.y - self.navigationBarView.frame.height
}, spring: 1,
velocity: 1,
options: .transitionCurlUp)
}
func present(on viewController: UIViewController) {
self.modalPresentationStyle = .overCurrentContext
self.modalTransitionStyle = .crossDissolve
viewController.present(self, animated: false, completion: nil)
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
SPAnimationSpring.animate(self.animationDuration, animations: {
self.view.backgroundColor = UIColor.black.withAlphaComponent(0)
self.confirmActionView.frame.origin.y = self.view.frame.size.height
self.cellsView.frame.origin.y = self.view.frame.size.height
self.navigationBarView.frame.origin.y = self.view.frame.size.height
}, spring: 1,
velocity: 1,
options: .transitionCurlDown,
withComplection: {
super.dismiss(animated: false) {
completion?()
}})
}
func addCell(task: String, title: String, subtitle: String? = nil, imageLink: String? = nil, image: UIImage? = nil) {
let cell = SPConfirmActionCellView.init(
task: task,
title: title,
subtitle: subtitle,
imageLink: imageLink,
image: image
)
self.cellsView.addSubview(cell)
}
@objc func hide() {
self.dismiss(animated: true)
}
@objc func tapConfirmButton() {
self.confirmedAction(self)
}
class SPConfirmActionNavigationBar: UIView {
let label: UILabel = UILabel.init()
let button: UIButton = UIButton.init()
let separatorView = UIView.init()
let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffect.Style.extraLight))
private var leftAndRightSpace: CGFloat {
return 18
}
init() {
super.init(frame: CGRect.zero)
self.addSubview(self.backgroundView)
self.separatorView.backgroundColor = UIColor.black.withAlphaComponent(0.15)
self.addSubview(self.separatorView)
self.label.numberOfLines = 1
self.label.textColor = UIColor.black
self.label.text = "SPConfirmAction"
self.label.font = UIFont.system(type: .DemiBold, size: 16)
self.addSubview(self.label)
self.button.setTitle("Сancel", for: .normal)
self.button.setTitleColorForNoramlAndHightlightedStates(color: SPNativeStyleKit.Colors.blue)
self.button.titleLabel?.font = UIFont.system(type: .DemiBold, size: 16)
self.addSubview(self.button)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func sizeToFit() {
super.sizeToFit()
self.setHeight(45)
self.layoutSubviews()
}
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundView.setEqualsBoundsFromSuperview()
self.label.sizeToFit()
self.label.frame.origin.x = self.leftAndRightSpace
self.label.center.y = self.frame.height / 2
self.button.sizeToFit()
self.button.frame.origin.x = self.frame.width - self.leftAndRightSpace - self.button.frame.width
self.button.center.y = self.frame.height / 2
self.separatorView.frame = CGRect.init(x: 0, y: self.frame.height - 1, width: self.frame.width, height: 1)
}
}
class SPConfirmActionCellView: UIView {
var taskLabel = UILabel.init()
var titleLabel: UILabel = UILabel.init()
var subtitleLabel: UILabel?
var imageView: SPDownloadingImageView?
var separatorView = UIView.init()
var yLeftPosition: CGFloat = 0
var baseSpace: CGFloat = 0
init(task: String, title: String, subtitle: String? = nil, imageLink: String? = nil, image: UIImage? = nil) {
super.init(frame: CGRect.zero)
self.separatorView.backgroundColor = UIColor.lightGray.withAlphaComponent(0.3)
self.addSubview(self.separatorView)
self.taskLabel.text = task.uppercased()
self.taskLabel.font = UIFont.system(type: .Medium, size: 13)
self.taskLabel.textColor = SPNativeStyleKit.Colors.gray
self.taskLabel.numberOfLines = 1
self.taskLabel.textAlignment = .right
self.addSubview(self.taskLabel)
self.titleLabel.text = title.uppercased()
self.titleLabel.font = UIFont.system(type: .Medium, size: 13)
self.titleLabel.textColor = UIColor.black
self.titleLabel.numberOfLines = 1
self.titleLabel.textAlignment = .left
self.addSubview(self.titleLabel)
if subtitle != nil {
self.subtitleLabel = UILabel.init()
self.subtitleLabel?.text = subtitle?.uppercased()
self.subtitleLabel?.font = UIFont.system(type: .Medium, size: 13)
self.subtitleLabel?.textColor = SPNativeStyleKit.Colors.gray
self.subtitleLabel?.numberOfLines = 0
self.subtitleLabel?.textAlignment = .left
self.addSubview(self.subtitleLabel!)
}
if imageLink != nil {
self.imageView = SPDownloadingImageView.init()
self.imageView?.setImage(link: imageLink!)
self.imageView?.layer.cornerRadius = 12
self.addSubview(self.imageView!)
}
if image != nil {
self.imageView = SPDownloadingImageView.init()
self.imageView?.setImage(image: image!, animatable: true)
self.imageView?.layer.cornerRadius = 12
self.addSubview(self.imageView!)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func sizeToFit() {
super.sizeToFit()
self.setHeight(45)
if self.imageView != nil {
self.setHeight(75)
}
if self.subtitleLabel != nil {
var height = 45 + self.subtitleLabel!.frame.height
height.setIfFewer(when: 75)
self.setHeight(height)
}
self.layoutSubviews()
}
override func layoutSubviews() {
super.layoutSubviews()
self.taskLabel.sizeToFit()
self.taskLabel.center.y = self.frame.height / 2
self.taskLabel.frame.origin.x = self.yLeftPosition - self.taskLabel.frame.width
self.titleLabel.sizeToFit()
self.titleLabel.center.y = self.frame.height / 2
self.titleLabel.frame.origin.x = self.yLeftPosition + self.baseSpace
self.titleLabel.setWidth(self.frame.width - self.titleLabel.frame.origin.x - self.baseSpace)
if subtitleLabel != nil {
let allContentHeight: CGFloat = self.titleLabel.frame.height + self.subtitleLabel!.frame.height + 3
self.titleLabel.frame.origin.y = (self.frame.height - allContentHeight) / 2
self.subtitleLabel?.frame.origin.x = self.titleLabel.frame.origin.x
self.subtitleLabel?.frame.origin.y = self.titleLabel.frame.bottomYPosition + 3
self.subtitleLabel?.sizeToFit()
self.subtitleLabel?.setWidth(self.titleLabel.frame.width)
}
if imageView != nil {
self.taskLabel.frame = CGRect.zero
var imageSideSize = self.frame.height - 10 * 2
imageSideSize.setIfMore(when: 40)
self.imageView?.frame = CGRect.init(x: self.yLeftPosition - imageSideSize, y: 0, width: imageSideSize, height: imageSideSize)
self.imageView?.center.y = self.frame.height / 2
}
self.separatorView.frame = CGRect.init(x: 18, y: self.frame.height - 1, width: self.frame.width - 18, height: 1)
}
}
class SPConfirmActionCellsView: UIView {
let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffect.Style.extraLight))
private var baseSpace: CGFloat = 18
init() {
super.init(frame: CGRect.zero)
self.addSubview(self.backgroundView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundView.setEqualsBoundsFromSuperview()
var maxLabelWidth: CGFloat = 0
for view in self.subviews {
if view != self.backgroundView {
if let cellView = view as? SPConfirmActionCellView {
cellView.sizeToFit()
if maxLabelWidth < cellView.taskLabel.frame.width {
maxLabelWidth = cellView.taskLabel.frame.width
}
}
}
}
for view in self.subviews {
if view != self.backgroundView {
if let cellView = view as? SPConfirmActionCellView {
cellView.yLeftPosition = self.baseSpace * 2 + maxLabelWidth
cellView.baseSpace = self.baseSpace
cellView.layoutSubviews()
}
}
}
var yPosition: CGFloat = 0
for view in subviews {
if view != self.backgroundView {
view.sizeToFit()
view.setWidth(self.frame.width)
view.frame.origin = CGPoint.init(x: 0, y: yPosition)
yPosition += view.frame.height
}
}
self.setHeight(yPosition)
}
override func sizeToFit() {
super.sizeToFit()
var height: CGFloat = 0
for view in subviews {
if view != self.backgroundView {
view.sizeToFit()
height += view.frame.height
}
}
self.setHeight(height)
self.layoutSubviews()
}
}
class SPConfirmActionButtonView: UIView {
let button = SPAppStoreActionButton()
let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffect.Style.extraLight))
init() {
super.init(frame: CGRect.zero)
self.button.style = .buyInStore
self.button.setTitle("Execute", for: .normal)
self.button.titleLabel?.font = UIFont.system(type: .DemiBold, size: 16)
self.addSubview(self.backgroundView)
self.addSubview(self.button)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func sizeToFit() {
super.sizeToFit()
var baseHeight: CGFloat = 80
if #available(iOS 11.0, *) {
baseHeight += (self.superview?.safeAreaInsets.bottom ?? 0)
}
self.setHeight(baseHeight)
self.layoutSubviews()
}
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundView.setEqualsBoundsFromSuperview()
self.button.sizeToFit()
self.button.setXCenteringFromSuperview()
var safeAreaInsetsBottom: CGFloat = 0
if #available(iOS 11.0, *) {
safeAreaInsetsBottom = (self.superview?.safeAreaInsets.bottom ?? 0)
}
self.button.center.y = (self.frame.height - safeAreaInsetsBottom) / 2
}
}
}
@@ -0,0 +1,76 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPHiderViewController: SPBaseViewController {
let backgroundView = SPGradeBlurView.init()
private var durationAnimation: TimeInterval = 0.3
override func viewDidLoad() {
super.viewDidLoad()
self.statusBar = .light
self.view.backgroundColor = UIColor.clear
self.backgroundView.setGradeColor(UIColor.black)
self.activityIndicatorView.alpha = 0
self.backgroundView.setGradeAlpha(0, blurRaius: 0)
self.view.addSubview(self.backgroundView)
self.activityIndicatorView.startAnimating()
self.view.addSubview(self.activityIndicatorView)
self.updateLayout(with: self.view.frame.size)
}
override func updateLayout(with size: CGSize) {
self.backgroundView.frame = CGRect.init(origin: CGPoint.zero, size: size)
self.activityIndicatorView.center = CGPoint.init(x: size.width / 2, y: size.height / 2)
}
func present(on viewController: UIViewController) {
self.modalTransitionStyle = .crossDissolve
self.modalPresentationStyle = .overCurrentContext
viewController.present(self, animated: false) {
SPAnimation.animate(self.durationAnimation, animations: {
self.backgroundView.setGradeAlpha(0.5, blurRaius: 8)
})
SPAnimation.animate(self.durationAnimation * 1.5, animations: {
self.activityIndicatorView.alpha = 1
}, delay: self.durationAnimation / 1.5)
}
}
override func dismiss() {
SPAnimation.animate(self.durationAnimation / 1.2, animations: {
self.activityIndicatorView.alpha = 0
})
SPAnimation.animate(self.durationAnimation, animations: {
self.backgroundView.setGradeAlpha(0, blurRaius: 0)
}, delay: 0) {
self.dismiss(animated: false, completion: nil)
}
}
}
@@ -0,0 +1,271 @@
// 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 SPNativeTableViewController: SPBaseTableViewController {
let labelTableViewCellIdentifier: String = "labelTableViewCellIdentifier"
let textFieldTableViewCellIdentifier: String = "textFieldTableViewCellIdentifier"
let buttonTableViewCellIdentifier: String = "buttonTableViewCellIdentifier"
let textTableViewCellIdentifier: String = "textTableViewCellIdentifier"
let textInputTableViewCellIdentifier: String = "textInputTableViewCellIdentifier"
let promoTableViewCellIdentifier: String = "promoTableViewCellIdentifier"
let featuredTitleTableViewCellIdentifier: String = "featuredTitleTableViewCellIdentifier"
let mailTableViewCellIdentifier: String = "mailTableViewCellIdentifier"
let collectionImagesTableViewCellIdentifier: String = "collectionImagesTableViewCellIdentifier"
let imageTableViewCellIdentifier: String = "imageTableViewCellIdentifier"
let proposeTableViewCellIdentifier: String = "proposeTableViewCellIdentifier"
let mengTransformTableViewCell = "mengTransformTableViewCell"
var showTopInsets: Bool = true
var showBottomInsets: Bool = true
var autoTopSpace: Bool = true
var autoBottomSpace: Bool = true
private var autoSpaceHeight: CGFloat = 35
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.statusBar = .dark
self.tableView = UITableView.init(frame: self.view.bounds, style: UITableView.Style.grouped)
self.setPrefersLargeNavigationTitle("Title")
if #available(iOS 11.0, *) {
self.tableView.contentInsetAdjustmentBehavior = .always
}
self.tableView.backgroundColor = SPNativeStyleKit.Colors.customGray
self.tableView.delaysContentTouches = false
self.tableView.allowsSelection = false
self.tableView.rowHeight = UITableView.automaticDimension
self.tableView.sectionFooterHeight = UITableView.automaticDimension
self.tableView.sectionHeaderHeight = UITableView.automaticDimension
self.tableView.estimatedRowHeight = 44
self.tableView.register(SPFormTextFiledTableViewCell.self, forCellReuseIdentifier: self.textFieldTableViewCellIdentifier)
self.tableView.register(SPFormLabelTableViewCell.self, forCellReuseIdentifier: self.labelTableViewCellIdentifier)
self.tableView.register(SPFormButtonTableViewCell.self, forCellReuseIdentifier: self.buttonTableViewCellIdentifier)
self.tableView.register(SPFormTextTableViewCell.self, forCellReuseIdentifier: self.textTableViewCellIdentifier)
self.tableView.register(SPFormTextInputTableViewCell.self, forCellReuseIdentifier: self.textInputTableViewCellIdentifier)
self.tableView.register(SPPromoTableViewCell.self, forCellReuseIdentifier: self.promoTableViewCellIdentifier)
self.tableView.register(SPFormFeaturedTitleTableViewCell.self, forCellReuseIdentifier: self.featuredTitleTableViewCellIdentifier)
self.tableView.register(SPFormMailTableViewCell.self, forCellReuseIdentifier: self.mailTableViewCellIdentifier)
self.tableView.register(SPCollectionImagesTableViewCell.self, forCellReuseIdentifier: self.collectionImagesTableViewCellIdentifier)
self.tableView.register(SPImageTableViewCell.self, forCellReuseIdentifier: self.imageTableViewCellIdentifier)
self.tableView.register(SPProposeTableViewCell.self, forCellReuseIdentifier: self.proposeTableViewCellIdentifier)
self.tableView.register(SPMengTransformTableViewCell.self, forCellReuseIdentifier: self.mengTransformTableViewCell)
self.activityIndicatorView.stopAnimating()
self.activityIndicatorView.color = SPNativeStyleKit.Colors.gray
self.view.addSubview(self.activityIndicatorView)
self.updateLayout(with: self.view.frame.size)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return super.numberOfSections(in: tableView)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return super.tableView(tableView, numberOfRowsInSection: section)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
fatalError("SPNativeTableViewController - need ivveride cellForRowAt")
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return nil
}
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return nil
}
func dequeueTextFiledTableViewCell(indexPath: IndexPath) -> SPFormTextFiledTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.textFieldTableViewCellIdentifier, for: indexPath as IndexPath) as! SPFormTextFiledTableViewCell
}
func dequeueLabelTableViewCell(indexPath: IndexPath) -> SPFormLabelTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.labelTableViewCellIdentifier, for: indexPath as IndexPath) as! SPFormLabelTableViewCell
}
func dequeueButtonTableViewCell(indexPath: IndexPath) -> SPFormButtonTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.buttonTableViewCellIdentifier, for: indexPath as IndexPath) as! SPFormButtonTableViewCell
}
func dequeueTextTableViewCell(indexPath: IndexPath) -> SPFormTextTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.textTableViewCellIdentifier, for: indexPath as IndexPath) as! SPFormTextTableViewCell
}
func dequeueTextInputTableViewCell(indexPath: IndexPath) -> SPFormTextInputTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: self.textInputTableViewCellIdentifier, for: indexPath as IndexPath) as! SPFormTextInputTableViewCell
cell.currentIndexPath = indexPath
return cell
}
func dequeuePromoTableViewCell(indexPath: IndexPath? = nil) -> SPPromoTableViewCell {
if indexPath == nil {
return tableView.dequeueReusableCell(withIdentifier: self.promoTableViewCellIdentifier) as! SPPromoTableViewCell
} else {
return tableView.dequeueReusableCell(withIdentifier: self.promoTableViewCellIdentifier, for: indexPath! as IndexPath) as! SPPromoTableViewCell
}
}
func dequeueFeaturedTitleTableViewCell(indexPath: IndexPath) -> SPFormFeaturedTitleTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.featuredTitleTableViewCellIdentifier, for: indexPath as IndexPath) as! SPFormFeaturedTitleTableViewCell
}
func dequeueMailTableViewCell(indexPath: IndexPath) -> SPFormMailTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: self.mailTableViewCellIdentifier, for: indexPath as IndexPath) as! SPFormMailTableViewCell
cell.currentIndexPath = indexPath
return cell
}
func dequeueCollectionImagesTableViewCell(indexPath: IndexPath) -> SPCollectionImagesTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.collectionImagesTableViewCellIdentifier, for: indexPath as IndexPath) as! SPCollectionImagesTableViewCell
}
func dequeueImageTableViewCell(indexPath: IndexPath) -> SPImageTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: self.imageTableViewCellIdentifier, for: indexPath as IndexPath) as! SPImageTableViewCell
cell.currentIndexPath = indexPath
return cell
}
func dequeueProposeTableViewCell(indexPath: IndexPath) -> SPProposeTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.proposeTableViewCellIdentifier, for: indexPath as IndexPath) as! SPProposeTableViewCell
}
func dequeueMengTransformTableViewCell(indexPath: IndexPath) -> SPMengTransformTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.mengTransformTableViewCell, for: indexPath as IndexPath) as! SPMengTransformTableViewCell
}
}
//MARK: - manage selection
extension SPNativeTableViewController {
override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
if let _ = tableView.cellForRow(at: indexPath) as? SPFormFeaturedTitleTableViewCell {
return false
}
return true
}
}
//MARK: - hide button
extension SPNativeTableViewController {
func addHideButton(title: String) {
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(
title: title,
style: UIBarButtonItem.Style.done,
target: self,
action: #selector(self.dismiss(sender:))
)
}
}
//MARK: - manage spaces
extension SPNativeTableViewController {
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
return self.showTopInsets ? super.tableView(tableView, viewForHeaderInSection: section) : nil
} else {
return super.tableView(tableView, viewForHeaderInSection: section)
}
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
if section == self.tableView.lastSection {
return self.showBottomInsets ? super.tableView(tableView, viewForFooterInSection: section) : nil
} else {
return super.tableView(tableView, viewForFooterInSection: section)
}
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
let firstSection = self.tableView.firstSectionWithRows
if section == firstSection {
if self.showTopInsets {
if self.autoTopSpace {
if self.tableView(self.tableView, viewForHeaderInSection: firstSection!) != nil {
return UITableView.automaticDimension
}
if self.tableView(self.tableView, titleForHeaderInSection: firstSection!) != nil {
return UITableView.automaticDimension
}
return self.autoSpaceHeight
} else {
return UITableView.automaticDimension
}
} else {
return 0
}
} else {
if self.tableView.numberOfRows(inSection: section) == 0 {
return 0
} else {
return UITableView.automaticDimension
}
}
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
if section == self.tableView.lastSectionWithRows {
if self.showBottomInsets {
if self.autoBottomSpace {
if self.tableView(self.tableView, viewForFooterInSection: self.tableView.lastSectionWithRows!) != nil {
return UITableView.automaticDimension
}
if self.tableView(self.tableView, titleForFooterInSection: self.tableView.lastSectionWithRows!) != nil {
return UITableView.automaticDimension
}
return self.autoSpaceHeight
} else {
return UITableView.automaticDimension
}
} else {
return 0
}
} else {
if self.tableView.numberOfRows(inSection: section) == 0 {
return 0
} else {
return UITableView.automaticDimension
}
}
}
}
@@ -0,0 +1,94 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPProgressLoadingViewController: SPBaseViewController {
let titleLabel = UILabel.init()
let subtitleLabel = UILabel.init()
let progressView = UIProgressView.init(progressViewStyle: UIProgressView.Style.default)
let commentLabel = UILabel.init()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = SPNativeStyleKit.Colors.white
self.titleLabel.text = "Title"
self.titleLabel.font = UIFont.system(type: UIFont.BoldType.Medium, size: 23)
self.titleLabel.textColor = SPNativeStyleKit.Colors.black
self.titleLabel.numberOfLines = 1
self.view.addSubview(self.titleLabel)
self.subtitleLabel.text = "Subtitle..."
self.subtitleLabel.setCenteringAlignment()
self.subtitleLabel.font = UIFont.system(type: UIFont.BoldType.Regular, size: 13)
self.subtitleLabel.textColor = SPNativeStyleKit.Colors.gray
self.subtitleLabel.numberOfLines = 1
self.view.addSubview(self.subtitleLabel)
self.progressView.progress = 0
self.view.addSubview(self.progressView)
self.activityIndicatorView.color = SPNativeStyleKit.Colors.gray
self.activityIndicatorView.startAnimating()
self.view.addSubview(self.activityIndicatorView)
self.commentLabel.text = "Comment"
self.commentLabel.setCenteringAlignment()
self.commentLabel.font = UIFont.system(type: UIFont.BoldType.Regular, size: 11)
self.commentLabel.textColor = SPNativeStyleKit.Colors.gray
self.commentLabel.numberOfLines = 1
self.view.addSubview(self.commentLabel)
self.updateLayout(with: self.view.frame.size)
}
func set(progress: Float) {
self.progressView.setProgress(progress, animated: true)
}
override func updateLayout(with size: CGSize) {
self.titleLabel.sizeToFit()
self.titleLabel.center.x = size.width / 2
self.subtitleLabel.sizeToFit()
self.subtitleLabel.center.x = size.width / 2
self.progressView.setWidth(size.width * 0.55)
self.progressView.center.x = size.width / 2
self.activityIndicatorView.sizeToFit()
self.activityIndicatorView.center.x = size.width / 2
let allHeight = self.titleLabel.frame.height + 5 + self.subtitleLabel.frame.height + 30 + self.progressView.frame.height + 30 + self.activityIndicatorView.frame.height
self.titleLabel.frame.origin.y = (size.height - allHeight - self.topSafeArea) / 2
self.subtitleLabel.frame.origin.y = self.titleLabel.frame.bottomYPosition + 5
self.progressView.frame.origin.y = self.subtitleLabel.frame.bottomYPosition + 30
self.activityIndicatorView.frame.origin.y = self.progressView.frame.bottomYPosition + 30
self.commentLabel.sizeToFit()
self.commentLabel.center.x = size.width / 2
self.commentLabel.frame.origin.y = size.height - (self.view.bottomSafeArea + self.commentLabel.frame.height + 5)
}
}
@@ -0,0 +1,269 @@
// 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 SPProposeViewController: SPBaseViewController {
private let data: Data
internal let areaView = AreaView()
private var animationDuration: TimeInterval {
return 0.5
}
private var space: CGFloat {
return 6
}
init(title: String, subtitle: String, buttonTitle: String, imageLink: String? = nil, image: UIImage? = nil, complection: @escaping (_ isConfirmed: Bool)->() = {_ in }) {
self.data = Data(
title: title,
subtitle: subtitle,
buttonTitle: buttonTitle,
imageLink: imageLink,
image: image,
complection: complection
)
super.init(nibName: nil, bundle: nil)
self.modalPresentationStyle = .overCurrentContext
self.view.backgroundColor = UIColor.black.withAlphaComponent(0)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.areaView.isHidden = true
self.areaView.titleLabel.text = self.data.title
self.areaView.subtitleLabel.text = self.data.subtitle
self.areaView.button.setTitle(self.data.buttonTitle, for: UIControl.State.normal)
self.areaView.button.addTarget(self, action: #selector(self.open), for: UIControl.Event.touchUpInside)
self.areaView.closeButton.addTarget(self, action: #selector(self.close), for: UIControl.Event.touchUpInside)
self.view.addSubview(self.areaView)
let panGesture = UIPanGestureRecognizer.init(target: self, action: #selector(self.handleGesture(sender:)))
panGesture.maximumNumberOfTouches = 1
self.areaView.addGestureRecognizer(panGesture)
self.areaView.imageView.setParalax(amount: 0.1)
if let image = self.data.image {
self.areaView.imageView.setImage(image: image, animatable: false)
}
self.updateLayout(with: self.view.frame.size)
}
override func updateLayout(with size: CGSize) {
self.areaView.setWidth(size.width - (self.space * 2))
self.areaView.layoutSubviews()
self.areaView.sizeToFit()
self.areaView.frame.origin.x = self.space
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (UIWindow.bottomSafeArea / 2) - self.space
}
func present(on viewController: UIViewController) {
viewController.present(self, animated: false, completion: {
SPVibration.impact(SPVibration.Mode.warning)
self.areaView.frame.origin.y = self.view.frame.size.height
self.areaView.isHidden = false
SPAnimationSpring.animate(self.animationDuration, animations: {
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (UIWindow.bottomSafeArea / 2) - self.space
}, spring: 1,
velocity: 1,
options: .transitionCurlUp)
})
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
let hide = {
self.view.backgroundColor = UIColor.black.withAlphaComponent(0)
self.areaView.frame.origin.y = self.view.frame.size.height
}
let dismiss = {
super.dismiss(animated: false) {
completion?()
}
}
if flag {
SPAnimationSpring.animate(self.animationDuration, animations: {
hide()
}, spring: 1,
velocity: 1,
options: .transitionCurlDown,
withComplection: {
dismiss()
})
} else {
hide()
dismiss()
}
}
@objc func handleGesture(sender: UIPanGestureRecognizer) {
let returnAreaViewToPoint = {
SPAnimationSpring.animate(self.animationDuration, animations: {
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (UIWindow.bottomSafeArea / 2) - self.space
}, spring: 1,
velocity: 1,
options: .transitionCurlDown,
withComplection: {
})
}
switch sender.state {
case .began:
break
case .cancelled:
returnAreaViewToPoint()
case .changed:
let translation = sender.translation(in: self.view)
self.areaView.center = CGPoint(x: areaView.center.x + 0, y: areaView.center.y + translation.y / 4)
sender.setTranslation(CGPoint.zero, in: self.view)
case .ended:
returnAreaViewToPoint()
default:
break
}
}
@objc func open() {
self.data.complection(true)
self.dismiss(animated: true)
}
@objc func close() {
self.data.complection(false)
self.dismiss(animated: true)
}
class AreaView: UIView {
let titleLabel = UILabel()
let subtitleLabel = UILabel()
let imageView = SPDownloadingImageView()
let button = SPNativeOS11Button()
let closeButton = SPSystemIconButton(type: SPSystemIconType.close)
var imageSideSize: CGFloat = 160
private let space: CGFloat = 36
init() {
super.init(frame: CGRect.zero)
self.backgroundColor = UIColor.white
self.layer.masksToBounds = true
self.layer.cornerRadius = 34
self.titleLabel.font = UIFont.system(type: .Regular, size: 28)
self.titleLabel.textColor = UIColor.init(hex: "939393")
self.titleLabel.numberOfLines = 1
self.titleLabel.adjustsFontSizeToFitWidth = true
self.titleLabel.minimumScaleFactor = 0.5
self.titleLabel.setCenteringAlignment()
self.addSubview(self.titleLabel)
self.subtitleLabel.font = UIFont.system(type: .Regular, size: 16)
self.subtitleLabel.textColor = SPNativeStyleKit.Colors.black
self.subtitleLabel.numberOfLines = 0
self.subtitleLabel.setCenteringAlignment()
self.addSubview(self.subtitleLabel)
self.imageView.gradeView.backgroundColor = UIColor.white
self.imageView.contentMode = .scaleAspectFit
self.imageView.layer.masksToBounds = true
self.addSubview(self.imageView)
self.button.titleLabel?.font = UIFont.system(type: UIFont.BoldType.Medium, size: 15)
self.button.setTitleColorForNoramlAndHightlightedStates(color: SPNativeStyleKit.Colors.black)
self.button.backgroundColor = UIColor.init(hex: "D4D3DB")
self.addSubview(self.button)
self.closeButton.widthIconFactor = 0.4
self.closeButton.heightIconFactor = 0.4
self.closeButton.backgroundColor = UIColor.init(hex: "EFEFF4")
self.closeButton.color = UIColor.init(hex: "979797")
self.addSubview(self.closeButton)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.titleLabel.sizeToFit()
self.titleLabel.frame.origin.y = self.space * 0.8
self.titleLabel.setWidth(self.frame.width - self.space * 3)
self.titleLabel.setXCenteringFromSuperview()
self.subtitleLabel.sizeToFit()
self.subtitleLabel.frame.origin.y = self.titleLabel.frame.bottomYPosition + 8
self.subtitleLabel.setWidth(self.frame.width - self.space * 2)
self.subtitleLabel.setXCenteringFromSuperview()
self.imageView.frame = CGRect.init(
x: 0, y: self.subtitleLabel.frame.bottomYPosition + self.space / 2,
width: self.imageSideSize,
height: self.imageSideSize
)
self.imageView.setXCenteringFromSuperview()
self.button.sizeToFit()
self.button.setWidth(self.frame.width - self.space * 2)
self.button.frame.origin.y = self.imageView.frame.bottomYPosition + self.space / 2
self.button.setXCenteringFromSuperview()
self.closeButton.frame = CGRect.init(x: 0, y: 0, width: 24, height: 24)
self.closeButton.frame.origin.x = self.frame.width - self.closeButton.frame.width - 20
self.closeButton.frame.origin.y = 20
self.closeButton.round()
}
override func sizeToFit() {
super.sizeToFit()
self.setHeight(self.button.frame.bottomYPosition + self.space)
}
}
struct Data {
var title: String
var subtitle: String
var buttonTitle: String
var imageLink: String?
var image: UIImage?
var complection: (_ isConfirmed: Bool)->()
}
}
@@ -0,0 +1,31 @@
import UIKit
struct SPRootViewController {
static var controller: UIViewController? {
return UIApplication.shared.keyWindow?.rootViewController
}
static func set(_ rootController: UIViewController, animatable: Bool = true) {
rootController.view.frame = UIScreen.main.bounds
let replaceRootViewController = {
UIApplication.shared.keyWindow?.rootViewController = rootController
}
if animatable {
UIView.transition(
with: UIApplication.shared.keyWindow ?? UIWindow(),
duration: 0.5,
options: UIView.AnimationOptions.transitionCrossDissolve,
animations: {
replaceRootViewController()
}, completion: nil)
} else {
replaceRootViewController()
}
}
private init() {}
}

Some files were not shown because too many files have changed in this diff Show More