(of target: Any,
12 | matchingType type: T.Type = T.self,
13 | recursively: Bool = false,
14 | using closure: (T) -> Void) {
15 | let mirror = Mirror(reflecting: target)
16 |
17 | for child in mirror.children {
18 | (child.value as? T).map(closure)
19 |
20 | guard recursively else { continue }
21 | Mirror.reflectProperties(of: child.value,
22 | recursively: true,
23 | using: closure)
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/MutableCollection/MutableCollection+Shuffle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MutableCollection+Shuffle.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 11/05/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension MutableCollection where Self: RandomAccessCollection {
12 |
13 | /// In-place shuffling of self
14 | public mutating func shuffle() {
15 | var i = startIndex
16 | let beforeEndIndex = index(before: endIndex)
17 |
18 | while i < beforeEndIndex {
19 | let dist = distance(from: i, to: endIndex)
20 | let j = index(i, offsetBy: numericCast(arc4random_uniform(numericCast(dist))))
21 |
22 | guard i != j else { continue }
23 | swapAt(i, j)
24 | formIndex(after: &i)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/NSObject/NSObject+ClassName.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSObject+ClassName.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 02/01/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension NSObject {
12 |
13 | /// Contains name of a class
14 | public class var nameOfClass: String {
15 | return NSStringFromClass(self).components(separatedBy: ".").last!
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/NSObjectProtocol/NSObjectProtocol+KVO+KVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSObjectProtocol+KVO+KVC.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 24/01/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension NSObjectProtocol where Self: NSObject {
12 |
13 | @discardableResult public func observe
(for keyPath: KeyPath, onChange: @escaping (Value) -> ()) -> NSKeyValueObservation {
14 | return observe(keyPath, options: [.initial, .new], changeHandler: { (_, change) in
15 | guard let newValue = change.newValue else { return }
16 | onChange(newValue)
17 | })
18 | }
19 |
20 | @discardableResult public func bind(keyPath: KeyPath, to target: Target, at targetKeyPath: ReferenceWritableKeyPath) -> NSKeyValueObservation {
21 | return observe(for: keyPath, onChange: { target[keyPath: targetKeyPath] = $0 })
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/NotificationCenter/NotificationCenter+PostUtils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotificationCenter+PostUtils.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 11/01/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension NotificationCenter {
12 |
13 | /// Posts a notification by using the `default` notification center
14 | public static func post(notification name: Foundation.Notification.Name, object: Any? = nil) {
15 | NotificationCenter.default.post(name: name, object: object)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/Operaton/OperationQueue+MainUtils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OperationQueue+MainUtils.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 11/01/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension OperationQueue {
12 |
13 | /// Checks whether the current queue is the main
14 | static var isMain: Bool {
15 | return main == current
16 | }
17 |
18 | /// Executes an operation on the main operation queue
19 | static func onMain(_ operation: @escaping () -> Void) {
20 | OperationQueue.main.addOperation(operation)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/OptionSet/OptionSet+Operations.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OptionSet+Operations.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 29/08/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // MARK: - Extension that adds missing operations
12 | extension OptionSet where Element == Self {
13 |
14 | /// Duplicates the set, inserts the new element and returns Self
15 | public func inserting(new element: Self) -> Self {
16 | var opts = self
17 | opts.insert(element)
18 | return opts
19 | }
20 |
21 | /// Duplicates the set, removes the element and returns Self
22 | public func removing(_ element: Self) -> Self {
23 | var opts = self
24 | opts.remove(element)
25 | return opts
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/RandomAccessCollection/RandomAccessCollection+BinarySearch.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RandomAccessCollection+BinarySearch.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 25/11/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension RandomAccessCollection {
12 |
13 | /// Binary Search algorithm for a given value with the closure that specified the order of the elements
14 | public func binarySearch(for value: Iterator.Element,
15 | isOrderIncreasing: (Iterator.Element, Iterator.Element) -> Bool = { _, _ in true })
16 | -> Index? {
17 | guard !isEmpty else { return nil }
18 | var left = startIndex
19 | var right = index(before: endIndex)
20 |
21 | while left <= right {
22 | let dist = distance(from: left, to: right)
23 | let mid = index(left, offsetBy: dist/2)
24 | let candidate = self[mid]
25 |
26 | if isOrderIncreasing(candidate, value) { left = index(after: mid)
27 | } else if isOrderIncreasing(value, candidate) { right = index(before: mid)
28 | } else {
29 | // The elements are equal, do nothing
30 | }
31 | }
32 | // Element has not been found
33 | return nil
34 | }
35 |
36 | }
37 |
38 | extension RandomAccessCollection where Iterator.Element: Comparable {
39 |
40 | public func binarySearch(for value: Iterator.Element) -> Index? {
41 | return binarySearch(for: value, isOrderIncreasing: <)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/Sequence/Sequence+Count.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Sequence+Count.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 24/09/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Sequence {
12 |
13 | /// Performs a logical condition and counts all true results
14 | ///
15 | /// - Parameter logicalExpression: is a closure that performs contains a logical condition
16 | /// - Returns: the number of true logical expressions
17 | public func count(_ logicalExpression: (_ element: Element) -> Bool) -> Int {
18 | var counter = 0
19 | for element in self where logicalExpression(element) { counter += 1 }
20 | return counter
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/Sequence/Sequence+DuplicatesRemoved.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Sequence+DuplicatesRemoved.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 12/12/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Sequence where Element: Equatable {
12 |
13 | /// Removes the duplicate elements and returns the new Sequence without duplicates if any
14 | /// The complexity is O(log(n))
15 | public func removeDuplicates() -> [Element] {
16 | return reduce([], { $0.contains($1) ? $0 : $0 + [$1] })
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/Sequence/Sequence+Shuffle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Sequence+Shuffle.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 11/05/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Sequence {
12 |
13 | /// Shuffls the elements of self
14 | ///
15 | /// - Returns: an array of shuffled Elements
16 | public func randomlyShuffled() -> [Element] {
17 | var result = Array(self)
18 | result.shuffle()
19 | return result
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/String/String+Base64.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Base64.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 29/08/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 |
13 | /// Encodes self to Base64 String encoding
14 | ///
15 | /// - Returns: encoded Base64 String
16 | public func toBase64() -> String {
17 | return Data(utf8).base64EncodedString()
18 | }
19 |
20 | /// Decodes self from Base64 encoding
21 | ///
22 | /// - Returns: optional String that is decoded from Base64 encoding. Nil if the operation was unsuccessful
23 | public func fromBase64() -> String? {
24 | guard let data = Data(base64Encoded: self) else { return nil }
25 | return String(data: data, encoding: .utf8)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/String/String+Digits.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Digits.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 02/08/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 |
13 | /// Combines decimal digits into a single String property
14 | public var digits: String {
15 | return components(separatedBy: CharacterSet.decimalDigits.inverted)
16 | .joined()
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/String/String+FormattedDate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+FormattedDate.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 27/08/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 |
13 | /// Creates a Date instance from Self String based in the specified format
14 | ///
15 | /// - Parameter format: is a String format that is used in parsing
16 | /// - Returns: a Date instance
17 | public func date(inFormat format: String) -> Date? {
18 | let dateFormatter = DateFormatter()
19 | dateFormatter.dateFormat = format
20 | dateFormatter.locale = Locale(identifier: "en_US_POSIX")
21 | dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
22 |
23 | return dateFormatter.date(from: self)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/String/String+IndexOf.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+IndexOf.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 29/08/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 |
13 | /// Finds the first occurence for a given String
14 | public func index(of input: String,
15 | options: String.CompareOptions = .literal) -> String.Index? {
16 | return range(of: input, options: options)?.lowerBound
17 | }
18 |
19 | /// Finds the last occurence for a given String
20 | public func lastIndex(of input: String) -> String.Index? {
21 | return self.index(of: input, options: .backwards)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/String/String+Subscript.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Subscript.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 06/12/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // MARK: - Adds conformances to CoutableClosedRnage, CountableRange, PartialRangeThrough and PartialRangeFrom protocols support in a form of subscripts
12 | extension String {
13 |
14 | public subscript(value: CountableClosedRange) -> Substring {
15 | return self[index(startIndex, offsetBy: value.lowerBound)...index(startIndex, offsetBy: value.upperBound)]
16 | }
17 |
18 | public subscript(value: CountableRange) -> Substring {
19 | return self[index(startIndex, offsetBy: value.lowerBound)..) -> Substring {
23 | return self[..) -> Substring {
27 | return self[...index(startIndex, offsetBy: value.upperBound)]
28 | }
29 |
30 | public subscript(value: PartialRangeFrom) -> Substring {
31 | return self[index(startIndex, offsetBy: value.lowerBound)...]
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/String/String+Validation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Validation.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 29/08/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 |
13 | /// Determines if self is alphanumeric String
14 | ///
15 | /// - Returns: boolean value indicating wherher self is alphanumeric
16 | public var isAlphaNumeric: Bool {
17 | let alphaNumeric = NSCharacterSet.alphanumerics
18 | let output = self.unicodeScalars.split { !alphaNumeric.contains($0)}.map(String.init)
19 |
20 | if output.count == 1, output[0] != self {
21 | return false
22 | }
23 | return output.count == 1
24 | }
25 |
26 | /// Check whether the string is an email or not
27 | ///
28 | /// - Returns: boolean value indicating whether self is an email
29 | public var isEmail: Bool {
30 | let regex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
31 | let predicate = NSPredicate(format:"SELF MATCHES %@", regex)
32 | return predicate.evaluate(with: self)
33 | }
34 |
35 | /// Check whether the string contains one or more letters
36 | ///
37 | /// - Returns: boolean value indicating whether self has letters
38 | public var hasLetters: Bool {
39 | return rangeOfCharacter(from: .letters, options: .literal) != nil
40 | }
41 |
42 | /// Check whether the string contains one or more numbers
43 | ///
44 | /// - Returns: boolean value indicating whether self has numbers
45 | public var hasNumbers: Bool {
46 | return rangeOfCharacter(from: .decimalDigits, options: .literal) != nil
47 | }
48 |
49 | /// Check whether the string contains only letters
50 | ///
51 | /// - Returns: boolean value indicating whether self is alphabetic
52 | public var isAlphabetic: Bool {
53 | for c in self {
54 | if !(c >= "a" && c <= "z") && !(c >= "A" && c <= "Z") {
55 | return false
56 | }
57 | }
58 | return true
59 | }
60 |
61 | /// Check whether the string is a valid `JSON`
62 | ///
63 | /// - Returns: boolean value indicating whether self is `JSON`
64 | public var isJSON: Bool {
65 | guard
66 | let jsonDataToVerify = self.data(using: String.Encoding.utf8),
67 | let _ = try? JSONSerialization.jsonObject(with: jsonDataToVerify) else {
68 | return false
69 | }
70 | return true
71 | }
72 |
73 | /// Check whether the string is a valid `UUID`
74 | ///
75 | /// - Returns: boolean value indicating whether self is `UUID`
76 | var isUUID: Bool {
77 | return UUID(uuidString: self) != nil
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundation/URL/URL+QRImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URL+QRImage.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 08/01/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit.UIImage
11 | import CoreImage.CIImage
12 | import ExtendedCoreImage
13 |
14 | extension URL {
15 |
16 | /// Creates a QR image from the `absoluteString` of the `URL`
17 | ///
18 | /// - Parameters:
19 | /// - scale: is a `CGPoint` that specifies the scale affine transformation of the final QR image
20 | /// - color: is a `UIColor` that specifies the tint color (if any e.g. non `nil`) of the final QR image
21 | /// - Returns: optional `UIImage` instance holding the QR image or `nil` if `CIImage` could not be created
22 | public func qrImage(scaledBy scale: CGPoint = CGPoint(x: 10, y: 10), tint color: UIColor? = nil) -> UIImage? {
23 | var ciImage: CIImage? = CIImage.qrImage(from: absoluteString, scaledBy: scale)
24 |
25 | if let someColor = color {
26 | ciImage = ciImage?.tinted(by: someColor)
27 | }
28 |
29 | guard let image = ciImage else { return nil }
30 | return UIImage(ciImage: image)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundationDataStructures/Builder/BuilderProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BuilderProtocol.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 06/12/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Allows AnyObject to be extended with chainable initialization methods by using Keypath feature. Please note that the extension works only since `Swift 4.0`.
12 | public protocol BuilderProtocol { /* empty, implementation is added to the protocol extension*/ }
13 |
14 | #if swift(>=4.0)
15 | extension BuilderProtocol where Self: AnyObject {
16 |
17 | @discardableResult
18 | public func `init`(_ property: ReferenceWritableKeyPath, with value: T) -> Self {
19 | self[keyPath: property] = value
20 | return self
21 | }
22 | }
23 | #endif
24 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundationDataStructures/Dequeue/Dequeue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dequeue.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 01/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Dequeue {
12 | // MARK: - Private properties
13 |
14 | private var data: [T]
15 |
16 | // MARK: - Public properties
17 |
18 | public var count: Int {
19 | return data.count
20 | }
21 |
22 | public var capacity: Int {
23 | get {
24 | return data.capacity
25 | }
26 | set {
27 | data.reserveCapacity(newValue)
28 | }
29 | }
30 |
31 | // MARK: - Initializers
32 |
33 | public init() {
34 | data = []
35 | }
36 |
37 | public init(_ elements: S) where S.Iterator.Element == T {
38 | self.init()
39 | data.append(contentsOf: elements)
40 | }
41 |
42 | // MARK: - Methods
43 |
44 | public mutating func dequeueFront() -> T? {
45 | return data.removeFirst()
46 | }
47 |
48 | public mutating func dequeueBack() -> T? {
49 | return data.removeLast()
50 | }
51 |
52 | public mutating func enqueue(front element: T) {
53 | data.insert(element, at: 0)
54 | }
55 |
56 | public mutating func enqueue(back element: T) {
57 | data.append(element)
58 | }
59 |
60 | public mutating func clear() {
61 | data.removeAll()
62 | }
63 |
64 | public mutating func isFull() -> Bool {
65 | return count == data.capacity
66 | }
67 |
68 | public func isEmpty() -> Bool {
69 | return data.isEmpty
70 | }
71 |
72 | public func peekFirst() -> T? {
73 | return data.first
74 | }
75 |
76 | public func peekLast() -> T? {
77 | return data.last
78 | }
79 |
80 | // MARK: - Private methods
81 |
82 | private func checkIndex(index: Int) throws {
83 | if index < 0 || index > count {
84 | throw DequeueError.indexOutOfRange
85 | }
86 | }
87 | }
88 |
89 | enum DequeueError: Error {
90 | case indexOutOfRange
91 | }
92 |
93 | extension Dequeue: CustomStringConvertible, CustomDebugStringConvertible {
94 |
95 | public var description: String {
96 | return data.description
97 | }
98 |
99 | public var debugDescription: String {
100 | return data.description
101 | }
102 | }
103 |
104 | extension Dequeue: ExpressibleByArrayLiteral {
105 |
106 | public init(arrayLiteral elements: T...) {
107 | self.init(elements)
108 | }
109 | }
110 |
111 | extension Dequeue: Sequence {
112 |
113 | public func makeIterator() -> AnyIterator {
114 | let indexedIterator = IndexingIterator(_elements: data.lazy)
115 | return AnyIterator(indexedIterator)
116 | }
117 |
118 | public func generate() -> AnyIterator {
119 | let indexingIteratoer = IndexingIterator(_elements: data.lazy)
120 | return AnyIterator(indexingIteratoer)
121 |
122 | }
123 | }
124 |
125 |
126 | extension Dequeue: MutableCollection {
127 |
128 | // MARK: - Core protocol conformance
129 |
130 | public var startIndex: Int {
131 | return 0
132 | }
133 |
134 | public var endIndex: Int {
135 | return count - 1
136 | }
137 |
138 | public func index(after i: Int) -> Int {
139 | return data.index(after: i)
140 | }
141 |
142 | // MARK: - Subscript implementation
143 |
144 | public subscript(index: Int) -> T {
145 | get {
146 | checkHandledIndex(index: index)
147 | return data[index]
148 | }
149 | set {
150 | checkHandledIndex(index: index)
151 | data[index] = newValue
152 | }
153 | }
154 |
155 | // MARK: - Utility method
156 |
157 | private func checkHandledIndex(index: Int) {
158 | do {
159 | try checkIndex(index: index)
160 | } catch {
161 | fatalError(error.localizedDescription)
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundationDataStructures/DoublyLinkedList/DoublyNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DoublyNode.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 01/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class DoublyNode: CustomStringConvertible, CustomDebugStringConvertible {
12 |
13 | // MARK: - Conformances to CustomStringConvertible & CustomDebugStringConvertible protocols
14 |
15 | var description: String {
16 | return representation()
17 | }
18 |
19 | var debugDescription: String {
20 | return representation()
21 | }
22 |
23 | private func representation() -> String {
24 | return "\(String(describing: data))"
25 | }
26 |
27 | // MARK: - Properties
28 |
29 | var next: DoublyNode?
30 | var data: T
31 | var previous: DoublyNode?
32 |
33 | // MARK: - Initailziers
34 |
35 | init(data: T, next: DoublyNode?, previous: DoublyNode?) {
36 | self.data = data
37 | self.next = next
38 | self.previous = previous
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundationDataStructures/FunctionalLenses/Lens.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Lens.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 18/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // C -> Whole
12 | // V -> Part
13 | public struct Lens {
14 | public let get: (Whole) -> Part
15 | public let set: (Whole, Part) -> Whole
16 | }
17 |
18 | precedencegroup ComposePrecedence {
19 | associativity: left
20 | }
21 |
22 | infix operator >>> : ComposePrecedence
23 |
24 | public func >>> (lhs: Lens, rhs: Lens) -> Lens {
25 | return Lens(
26 | get: { container in
27 | let view = lhs.get(container)
28 | let subview = rhs.get(view)
29 | return subview
30 | },
31 | set: { (container, subview) in
32 | let initialView = lhs.get(container)
33 | let updatedView = rhs.set(initialView, subview)
34 | return lhs.set(container, updatedView)
35 | })
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundationDataStructures/ObjectPool/ObjectPoolItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjectPoolItem.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 28/09/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol ObjectPoolItem: class {
12 |
13 | /// Determines a rule that is used by the ObjectPool instnace to determine whether this object is eligible to be reused
14 | var canReuse: Bool { get }
15 |
16 | /// Resets the state of an instnace to the default state, so the next ObjectPool consumers will not have to deal with unexpected state of the enqueued object.
17 | ///
18 | /// WARNING: If you leave this method without an implementation, you may get unexpected behavior when using an instance of this object with ObjectPool
19 | func reset()
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundationDataStructures/Observer/Notification.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Notification.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 28/09/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol Notification: class {
12 | var data: Any? { get }
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundationDataStructures/Observer/Observer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Observer.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 28/09/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol Observer: class {
12 | func notify(with notification: Notification)
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundationDataStructures/PriorityQueue/PriorityQueue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PriorityQueue.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 01/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Implementation of Priority Queue based on Heap data structure.
12 | public struct PriorityQueue where T: Comparable {
13 |
14 | // MARK: - Propertioes
15 |
16 | fileprivate var heap: Heap
17 |
18 | // MARK: - Initializers
19 |
20 | public init(order: @escaping (T, T) -> Bool) {
21 | heap = Heap(order: order)
22 | }
23 |
24 | public init(elements: [T], order: @escaping (T, T) -> Bool) {
25 | heap = Heap(array: elements, order: order)
26 | }
27 |
28 | // MARK: - Methods
29 |
30 | public func isEmpty() -> Bool {
31 | return heap.isEmpty
32 | }
33 |
34 | public func count() -> Int {
35 | return heap.count
36 | }
37 |
38 | public func peek() -> T? {
39 | return heap.peek()
40 | }
41 |
42 | public mutating func enqueue(_ element: T) {
43 | heap.insert(node: element)
44 | }
45 |
46 | public mutating func dequeue() -> T? {
47 | return heap.remove()
48 | }
49 |
50 | public mutating func changePriority(index i: Int, value: T) {
51 | return heap.replace(elementAt: i, with: value)
52 | }
53 | }
54 |
55 | extension PriorityQueue where T: Equatable {
56 |
57 | // MARK: - Methods
58 |
59 | public func index(of element: T) -> Int? {
60 | return heap.index(of: element)
61 | }
62 | }
63 |
64 | extension PriorityQueue: ExpressibleByArrayLiteral {
65 |
66 | // MARK: - Initializers
67 |
68 | public init(arrayLiteral elements: T...) {
69 | self.init(elements: elements, order: >)
70 | }
71 | }
72 |
73 | extension PriorityQueue: CustomStringConvertible, CustomDebugStringConvertible {
74 |
75 | // MARK: - Properties
76 |
77 | public var description: String {
78 | return heap.description
79 | }
80 |
81 | public var debugDescription: String {
82 | return heap.debugDescription
83 | }
84 |
85 | }
86 |
87 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundationDataStructures/Stack/Stack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stack.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 30/09/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Stack {
12 |
13 | // MARK: - Private properties
14 |
15 | private var elements: [T]
16 |
17 | // MARK: - Public computed properties
18 |
19 | public var isEmpty: Bool {
20 | return elements.isEmpty
21 | }
22 |
23 | public var count: Int {
24 | return elements.count
25 | }
26 |
27 | // MARK: - Initializers
28 |
29 | public init() {
30 | elements = [T]()
31 | }
32 |
33 | public init(stack: S) where S.Iterator.Element == T {
34 | self.elements = Array(stack.reversed())
35 | }
36 |
37 | // MARK: Methods
38 |
39 | public mutating func push(element: T) {
40 | elements.append(element)
41 | }
42 |
43 | public mutating func peek() -> T? {
44 | return elements.last
45 | }
46 |
47 | public mutating func pop() -> T? {
48 | return elements.popLast()
49 | }
50 |
51 | }
52 |
53 | /// Extension that adds conformances to CustomStringConvertible & CustomDebugStringConvertible protocols
54 | extension Stack: CustomStringConvertible, CustomDebugStringConvertible {
55 |
56 | public var description: String {
57 | return elements.description
58 | }
59 |
60 | public var debugDescription: String {
61 | return elements.description
62 | }
63 | }
64 |
65 | /// Adds conformance to ExpressibleByArrayLiteral protocol - Stack can be initialized by an array literal
66 | extension Stack: ExpressibleByArrayLiteral {
67 |
68 | public init(arrayLiteral elements: T...) {
69 | self.init()
70 |
71 | elements.forEach { element in
72 | self.elements.append(element)
73 | }
74 | }
75 | }
76 |
77 | /// Adds conformance to Sequence protocol - can be used in for...in loop
78 | extension Stack: Sequence {
79 |
80 | public func makeIterator() -> AnyIterator {
81 | let idexedIterator = IndexingIterator(_elements: self.elements.lazy.reversed())
82 | return AnyIterator(idexedIterator)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundationSorting/Array+BubbleSort.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+BubbleSort.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 29/09/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Array where Element: Comparable {
12 |
13 | public mutating func bubbleSorted(order sign: (Element, Element) -> Bool) {
14 | let count = self.count
15 |
16 | for _ in 0...count {
17 | for value in 1...count - 1 {
18 | if sign(self[value - 1], self[value]) {
19 | // An alternative to the manual `swap`
20 | // self.swapAt(value - 1, value)
21 | let largerValue = self[value - 1]
22 | self[value - 1] = self[value]
23 | self[value] = largerValue
24 | }
25 | }
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundationSorting/Array+InsertionSort.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+InsertionSort.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 29/09/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Array where Element: Comparable {
12 |
13 | /// Sorts self in place with the given order (<, >) using the Insertion Sort algorithm
14 | public mutating func insertionSorted(order: (Element, Element) -> Bool) {
15 |
16 | if self.count <= 1 { return }
17 |
18 | for i in 1.. 0 && order(self[j - 1], temp) {
23 | self[j] = self[j - 1]
24 | j -= 1
25 | }
26 | self[j] = temp
27 | }
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundationSorting/Array+MergeSort.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+MergeSort.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 29/09/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Merge sort is a sorting algorithm that has lower order running time than the insertion sort algorithm. Conceptually it is a devide and conquer sorting algorithm.
12 | extension Array where Element: Comparable {
13 |
14 | // MARK: - Typealiases
15 |
16 | public typealias ComparisonClosure = (Element, Element) -> Bool
17 |
18 | // MARK: - Methods
19 |
20 | /// Sorts an array of Comparable elements using Merge sort algorithm
21 | ///
22 | /// - Parameter list: is an Array of Comparable elements
23 | /// - Returns: the same sorted Array
24 | public static func mergeSort(_ list: [Element], order sign: ComparisonClosure) -> [Element] {
25 |
26 | if list.count < 2 { return list }
27 | let center = (list.count) / 2
28 |
29 | let leftMergeSort = mergeSort([Element](list[0.. [Element] {
42 |
43 | var leftIndex = 0
44 | var rightIndex = 0
45 | let totalCapacity = lhalf.count + rhalf.count
46 |
47 | var temp = [Element]()
48 | temp.reserveCapacity(totalCapacity)
49 |
50 | while leftIndex < lhalf.count && rightIndex < rhalf.count {
51 | let leftElement = lhalf[leftIndex]
52 | let rightElement = rhalf[rightIndex]
53 |
54 | let leftGreatherThanRight = sign(rightElement, leftElement) // sign(leftElement, rightElement)
55 | let leftSmallerThanRight = !leftGreatherThanRight
56 |
57 | if leftGreatherThanRight {
58 | temp.append(leftElement)
59 | leftIndex += 1
60 | } else if leftSmallerThanRight {
61 | temp.append(rightElement)
62 | rightIndex += 1
63 | } else {
64 | temp.append(leftElement)
65 | temp.append(rightElement)
66 | leftIndex += 1
67 | rightIndex += 1
68 | }
69 | }
70 |
71 | temp += [Element](lhalf[leftIndex.. Int {
26 | let pivot = array[lowest]
27 | var i = lowest - 1
28 | var j = highest + 1
29 |
30 | while true {
31 | i += 1
32 |
33 | while array[i] < pivot { i += 1 }
34 | j -= 1
35 |
36 | while array[j] > pivot {j -= 1 }
37 | if i >= j { return j }
38 |
39 | array.swapAt(i, j)
40 | }
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundationSorting/Array+QuickSortLomutoScheme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+QuickSortLomutoScheme.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 29/09/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Array where Element: Comparable {
12 |
13 | public static func quickSortLomuto(array: inout [Element], lowest: Int, highest: Int) {
14 | if lowest < highest {
15 | let pivot = Array.partitionLomuto(array: &array, lowest: lowest, highest: highest)
16 |
17 | Array.quickSortLomuto(array: &array, lowest: lowest, highest: pivot - 1)
18 | Array.quickSortLomuto(array: &array, lowest: pivot + 1, highest: highest)
19 | } else {
20 | debugPrint(#function + " lowest param is bigger than highest: ", lowest, highest)
21 | }
22 | }
23 |
24 | private static func partitionLomuto(array: inout [Element], lowest: Int, highest: Int) -> Int {
25 | let pivot = array[highest]
26 | var i = lowest
27 |
28 | for j in lowest.. 0 {
28 | isDone = false
29 | }
30 | }
31 | digits *= base
32 | self = buckets.flatMap { $0 }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/ExtendedFoundationSorting/Array+ShellSort.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+ShellSort.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 29/09/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Array where Element: Comparable {
12 |
13 | // Shell sort is an improved version of insertion sort. The original list is broken into smaller sublists and then individually sorted using insertion sort.
14 | public mutating func shellSorted(order sign: (Element, Element) -> Bool) {
15 | var subcount = self.count / 2
16 |
17 | while subcount > 0 {
18 | for i in 0..= subcount && sign(self[j - subcount], temp) {
23 | self[j] = self[j - subcount]
24 | j -= subcount
25 | }
26 | self[j] = temp
27 | }
28 | subcount /= 2
29 | }
30 |
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/ExtendedPhotoKit/PHAsset/PHAsset+URL.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PHAsset+URL.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 13/06/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Photos
10 |
11 | public extension PHAsset {
12 |
13 | // MARK: - Enums
14 |
15 | enum PHAssetError: Error {
16 | case requestCannotBePerformed(String)
17 | }
18 |
19 | // MARK: - Methods
20 |
21 | /// Provides possibility to get URL for image and video media types.
22 | ///
23 | /// - Parameter completionHandler: is an escaping closure that returns a URL for a requested media type
24 | /// - Throws: PHAssetError with String description
25 | func url(completionHandler : @escaping ((_ responseURL : URL?) -> Void)) throws {
26 | switch mediaType {
27 | case .image :
28 | let options: PHContentEditingInputRequestOptions = PHContentEditingInputRequestOptions()
29 | options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
30 | return true
31 | }
32 | self.requestContentEditingInput(with: options, completionHandler: { (contentEditingInput, info) in
33 | completionHandler(contentEditingInput!.fullSizeImageURL)
34 | })
35 | case .video:
36 | let options: PHVideoRequestOptions = PHVideoRequestOptions()
37 | options.version = .original
38 | PHImageManager.default().requestAVAsset(forVideo: self, options: options, resultHandler: { (asset, audioMix, info) in
39 | if let urlAsset = asset as? AVURLAsset {
40 | let localVideoUrl = urlAsset.url
41 | completionHandler(localVideoUrl)
42 | } else {
43 | completionHandler(nil)
44 | }
45 | })
46 | default:
47 | throw PHAssetError.requestCannotBePerformed("The requested media type is not supported")
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/ExtendedSceneKit/SCNAction+MoveAlong.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SCNAction+MoveAlong.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 09/05/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import class SceneKit.SCNAction
10 | import struct SceneKit.SCNVector3
11 | import CoreGraphics
12 | import class UIKit.UIBezierPath
13 | import ExtendedUIKit
14 | import ExtendedCoreGraphics
15 |
16 | extension SCNAction {
17 |
18 | public class func moveAlong(path: UIBezierPath, z: CGFloat, speed: Double) -> SCNAction {
19 | let points = path.points
20 | var lastPoint = points.first!
21 | var lastAngle = lastPoint.angle(to: points[1])
22 | var actions = [SCNAction.rotateTo(x: 0, y: 0, z: lastAngle, duration: 0), SCNAction.move(to: SCNVector3(lastPoint.x, lastPoint.y, z), duration: 0)]
23 |
24 | for point in points[1...] {
25 | let duration = Double(point.distance(to: lastPoint)) / speed
26 | var angle = lastPoint.angle(to: point)
27 | if abs(angle - lastAngle) > .pi {
28 | if angle > 0 {
29 | angle -= 2 * .pi
30 | } else {
31 | angle += 2 * .pi
32 | }
33 | }
34 | actions.append(SCNAction.group([
35 | SCNAction.move(to: SCNVector3(point.x, point.y, z), duration: duration),
36 | SCNAction.rotateTo(x: 0, y: 0, z: angle, duration: duration / 3)
37 | ]))
38 | lastPoint = point
39 | lastAngle = angle
40 | }
41 | return SCNAction.sequence(actions)
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/ExtendedSceneKit/SCNVector3+Operators.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SCNVector3+Operators.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 11/05/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import SceneKit
10 |
11 | // MARK: - Standard operators
12 |
13 | func == (left: SCNVector3, right: SCNVector3) -> Bool {
14 | var isEqual = false
15 | if left.x == right.x && left.y == right.y && left.z == right.z {
16 | isEqual = true
17 | }
18 | return isEqual
19 | }
20 |
21 | func != (left: SCNVector3, right: SCNVector3) -> Bool {
22 | var isEqual = false
23 | if left.x == right.x && left.y == right.y && left.z == right.z {
24 | isEqual = true
25 | }
26 | return !isEqual
27 | }
28 |
29 | func + (left: SCNVector3, right: SCNVector3) -> SCNVector3 {
30 | return SCNVector3Make(left.x + right.x, left.y + right.y, left.z + right.z)
31 | }
32 |
33 | func += (left: inout SCNVector3, right: SCNVector3) {
34 | left = left + right
35 | }
36 |
37 | func - (left: SCNVector3, right: SCNVector3) -> SCNVector3 {
38 | return SCNVector3Make(left.x - right.x, left.y - right.y, left.z - right.z)
39 | }
40 |
41 | func -= (left: inout SCNVector3, right: SCNVector3) {
42 | left = left - right
43 | }
44 |
45 | func * (left: SCNVector3, right: SCNVector3) -> SCNVector3 {
46 | return SCNVector3Make(left.x * right.x, left.y * right.y, left.z * right.z)
47 | }
48 |
49 | func *= (left: inout SCNVector3, right: SCNVector3) {
50 | left = left * right
51 | }
52 |
53 | func * (vector: SCNVector3, scalar: Float) -> SCNVector3 {
54 | return SCNVector3Make(vector.x * scalar, vector.y * scalar, vector.z * scalar)
55 | }
56 |
57 | func * (vector: SCNVector3, scalar: CGFloat) -> SCNVector3 {
58 | let floatScalar = Float(scalar)
59 | return SCNVector3Make(vector.x * floatScalar, vector.y * floatScalar, vector.z * floatScalar)
60 | }
61 |
62 | func *= (vector: inout SCNVector3, scalar: Float) {
63 | vector = vector * scalar
64 | }
65 |
66 | func *= (vector: inout SCNVector3, scalar: CGFloat) {
67 | vector = vector * Float(scalar)
68 | }
69 |
70 | func / (left: SCNVector3, right: SCNVector3) -> SCNVector3 {
71 | return SCNVector3Make(left.x / right.x, left.y / right.y, left.z / right.z)
72 | }
73 |
74 | func /= (left: inout SCNVector3, right: SCNVector3) {
75 | left = left / right
76 | }
77 |
78 | func / (vector: SCNVector3, scalar: Float) -> SCNVector3 {
79 | return SCNVector3Make(vector.x / scalar, vector.y / scalar, vector.z / scalar)
80 | }
81 |
82 | func / (vector: SCNVector3, scalar: CGFloat) -> SCNVector3 {
83 | let floatScalar = Float(scalar)
84 | return SCNVector3Make(vector.x / floatScalar, vector.y / floatScalar, vector.z / floatScalar)
85 | }
86 |
87 | func /= (vector: inout SCNVector3, scalar: Float) {
88 | vector = vector / scalar
89 | }
90 |
91 | func /= (vector: inout SCNVector3, scalar: CGFloat) {
92 | vector = vector / Float(scalar)
93 | }
94 |
95 | // MARK: - Special operators
96 |
97 | infix operator +*
98 | infix operator +*=
99 |
100 | /// Cross product of two vectors
101 | func +* (left: SCNVector3, right: SCNVector3) -> SCNVector3 {
102 | let x = left.y * right.z - left.z * right.y
103 | let y = left.z * right.x - left.x * right.z
104 | let z = left.x * right.y - left.y * right.x
105 | return SCNVector3(x, y, z)
106 | }
107 |
108 | /// Cross product of two vectors
109 | func +*= (left: inout SCNVector3, right: SCNVector3) {
110 | let x = left.y * right.z - left.z * right.y
111 | let y = left.z * right.x - left.x * right.z
112 | let z = left.x * right.y - left.y * right.x
113 |
114 | left = SCNVector3(x, y, z)
115 | }
116 |
--------------------------------------------------------------------------------
/Sources/ExtendedSpriteKit/SKEmitterNode/SKEmitterNode+AdvanceSimulation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SKEmitterNode+AdvanceSimulation.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 20/05/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import SpriteKit.SKEmitterNode
10 |
11 | extension SKEmitterNode {
12 |
13 | /// Safely advance the simulation by checking if the node was previously paused and then advancing the simulation
14 | ///
15 | /// - Parameter sec: advance simulation in seconds
16 | func safeAdvanceSimulationTime(_ sec: TimeInterval) {
17 | let emitterPaused = self.isPaused
18 |
19 | if emitterPaused {
20 | self.isPaused = false
21 | }
22 | advanceSimulationTime(sec)
23 |
24 | if emitterPaused {
25 | self.isPaused = true
26 | }
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/Sources/ExtendedSpriteKit/SKScene/SKScene+ReferenceNodeFix.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SKScene+ReferenceNodeFix.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 03/11/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import SpriteKit
10 |
11 | extension SKScene {
12 |
13 | /// A small fix that resolves the default behavior for nodes that were referenced from differnet .sks files. The thing is that they do not launch their animations by default, so this small `hack` fixes this issue.
14 | ///
15 | /// The method should be called in `didMove(to view: SKView)`
16 | func launchReferenceAnimations() {
17 | isPaused = true
18 | isPaused = false
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/ExtendedSpriteKit/SKScene/SKScene+SerialSpriteLoading.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SKScene+SerialSpriteLoading.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 03/05/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import SpriteKit.SKScene
10 | import SpriteKit.SKNode
11 |
12 | public extension SKScene {
13 |
14 | /// Uploads a set of scene graph nodes with a specific pattern
15 | ///
16 | /// - Parameters:
17 | /// - key: is a String instnace that describes name of child nodes that will be uploaded
18 | /// - pattern: is a closure that accepts string and int (as key and index) and returns string that decribes naming pattern
19 | /// - indices: is an instnace of ClosedRange type that specifies index boundaries of uploading nodes (for instnace you want to upload a set of nodes that describe sky and are called "cloud" - there are 3 clouds: "cloud-1", "cloud-2", "cloud-3" - the method helps to upload them using a singe function)
20 | /// - Returns: an array containing Node types (Node is any type derived from SKNode class)s
21 | func upload(for key: String, with pattern: (_ key: String, _ index: Int)->String, inRange indices: ClosedRange) -> [Node] where Node: SKNode {
22 |
23 | var foundNodes = [Node]()
24 |
25 | for index in indices.lowerBound...indices.upperBound {
26 | let childName = pattern(key, index)
27 | guard let node = self.childNode(withName: childName) as? Node else {
28 | debugPrint(#function + " could not find child with the following name: ", childName)
29 | continue
30 | }
31 | foundNodes.append(node)
32 | }
33 |
34 | return foundNodes
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/ExtendedSpriteKit/SKTexture/SKTexture+LinearGradient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SKTexture+LinearGradient.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 06/05/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import SpriteKit.SKTexture
10 |
11 | extension SKTexture {
12 |
13 | enum GradientDirection {
14 | case up
15 | case left
16 | case upLeft
17 | case upRight
18 | }
19 |
20 | convenience init(size: CGSize, startColor: SKColor, endColor: SKColor, direction: GradientDirection = .up) {
21 | let context = CIContext(options: nil)
22 | let filter = CIFilter(name: "CILinearGradient")!
23 | let startVector: CIVector
24 | let endVector: CIVector
25 |
26 | filter.setDefaults()
27 |
28 | switch direction {
29 | case .up:
30 | startVector = CIVector(x: size.width/2, y: 0)
31 | endVector = CIVector(x: size.width/2, y: size.height)
32 | case .left:
33 | startVector = CIVector(x: size.width, y: size.height/2)
34 | endVector = CIVector(x: 0, y: size.height/2)
35 | case .upLeft:
36 | startVector = CIVector(x: size.width, y: 0)
37 | endVector = CIVector(x: 0, y: size.height)
38 | case .upRight:
39 | startVector = CIVector(x: 0, y: 0)
40 | endVector = CIVector(x: size.width, y: size.height)
41 | }
42 |
43 | filter.setValue(startVector, forKey: "inputPoint0")
44 | filter.setValue(endVector, forKey: "inputPoint1")
45 | filter.setValue(CIColor(color: startColor), forKey: "inputColor0")
46 | filter.setValue(CIColor(color: endColor), forKey: "inputColor1")
47 |
48 | let image = context.createCGImage(filter.outputImage!, from: CGRect(origin: .zero, size: size))
49 |
50 | self.init(cgImage: image!)
51 | }
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/Sources/ExtendedSpriteKit/SKTextureAtlas/SKTextureAtlas+FramesLoader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SKTextureAtlas+FramesLoader.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 03/05/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import SpriteKit.SKTextureAtlas
10 |
11 | extension SKTextureAtlas {
12 |
13 | /// Uploads an animation sequence from a texture atlas and returns an array of textures that can be futher used
14 | ///
15 | /// - Parameters:
16 | /// - named: is a texture atlas name
17 | /// - beginIndex: is a begin index that differentiates frames (for instnace the very first frame can named "player-0" or "player-1", the index helps in pattern matching)
18 | /// - pattern: is a closure that accepts name of a frame and index (index is incremented internally) and returns a string instnace that is used as texture atlas naming pattern
19 | /// - Returns: an array of SKTexture instances, each containing a singe frame of key-frame animation
20 | /// - Throws: an instnace of NSError with exit code 1, no user-related info and domain-specific error explanation
21 | class func upload(named name: String, beginIndex: Int = 1, pattern: (_ name: String, _ index: Int) -> String) throws -> [SKTexture] {
22 |
23 | let atlas = SKTextureAtlas(named: name)
24 | var frames = [SKTexture]()
25 | let count = atlas.textureNames.count
26 |
27 | if beginIndex > count {
28 | throw NSError(domain: "Begin index is grather than the number of texture in a the texture atlas named: \(name)", code: 1, userInfo: nil)
29 | }
30 |
31 | for index in beginIndex...count {
32 | let namePattern = pattern(name, index)
33 | let texture = atlas.textureNamed(namePattern)
34 | frames.append(texture)
35 | }
36 |
37 | return frames
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/Badge/Badge.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Badge.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 01/12/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | /// Allows to easily manage the UIApplication icon badge number status
13 | public struct Badge {
14 |
15 | private static let sharedApplication = UIApplication.shared
16 |
17 | public static var number: Int {
18 | set {
19 | sharedApplication.applicationIconBadgeNumber = newValue
20 | }
21 | get {
22 | return sharedApplication.applicationIconBadgeNumber
23 | }
24 | }
25 |
26 | public static func reset() {
27 | sharedApplication.applicationIconBadgeNumber = 0
28 | }
29 | }
30 | #endif
31 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/NSLayoutConstraint/NSLayoutConstraint+Activation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSLayoutConstraint+Activation.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 10/09/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | extension NSLayoutConstraint {
13 |
14 | /// Sets the priority of the constraint to the specified value and then return self
15 | @discardableResult public func with(priority: UILayoutPriority) -> Self {
16 | self.priority = priority
17 | return self
18 | }
19 |
20 | /// Set activation state for the current layout constrint and return self
21 | @discardableResult public func set(active: Bool) -> Self {
22 | isActive = active
23 | return self
24 | }
25 | }
26 | #endif
27 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/NSLayoutConstraint/NSLayoutConstraint+Animation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSLayoutConstraint+Animation.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 14/11/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension NSLayoutConstraint {
12 |
13 | public func setConstant(for value: CGFloat, animated: Bool = false, duration: TimeInterval = 0.3) {
14 | constant = value
15 |
16 | guard let first = firstItem as? UIView,
17 | let superview = first.superview else {
18 | return
19 | }
20 |
21 | if animated == false {
22 | superview.layoutIfNeeded()
23 | return
24 | }
25 |
26 | UIView.animate(withDuration: duration) { superview.layoutIfNeeded() }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIAlertController/UIAlertController+Presentation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIAlertController+Presentation.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 03/01/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit.UIAlertController
10 |
11 | extension UIAlertController {
12 |
13 | /// Presents a UIAlertController with the given title, message, tintColor and alert actions
14 | ///
15 | /// - Parameters:
16 | /// - title: is an optional String title that will appear at the top of the alert controller
17 | /// - message: is an optional String message that will appear at the center of the alert controller
18 | /// - tintColor: is an optional UIColor that will color the view of the alert controller
19 | /// - controller: is a target UIViewController that will be used to present the alert. If none was specified then the UIApplication's root view controller will be used to present the alert
20 | /// - animated: is a Bool value that specified whether the presentation of the alert should be animated or not
21 | /// - alertActions: is a closure that is used to inject varying number of UIAlertAction objects
22 | public class func present(with title: String?,
23 | and message: String?,
24 | tintColor: UIColor? = nil,
25 | from controller: UIViewController? = nil,
26 | shouldAnimate animated: Bool = true,
27 | alertActions: ()->[UIAlertAction]) {
28 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
29 |
30 | let actions = alertActions()
31 | actions.forEach { action in
32 | alertController.addAction(action)
33 | }
34 |
35 | if let tintColor = tintColor {
36 | alertController.view.tintColor = tintColor
37 | }
38 |
39 | let unwrappedController = controller ?? UIApplication.shared.keyWindow?.rootViewController
40 | unwrappedController?.present(alertController, animated: animated, completion: nil)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIApplication/UIApplication+SafeAreas.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIApplication+SafeAreas.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 09/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIApplication {
12 |
13 | public class func safeAreaBottom() -> CGFloat {
14 | let window = getFirstSharedAppsWindow()
15 | let bottomPadding = window?.safeAreaInsets.bottom ?? 0.0
16 | return bottomPadding
17 | }
18 |
19 | public class func safeAreaTop() -> CGFloat {
20 | let window = getFirstSharedAppsWindow()
21 | let bottomPadding = window?.safeAreaInsets.top ?? 0.0
22 | return bottomPadding
23 | }
24 |
25 | public class func safeAreaLeft() -> CGFloat {
26 | let window = getFirstSharedAppsWindow()
27 | let leftPadding = window?.safeAreaInsets.left ?? 0.0
28 | return leftPadding
29 | }
30 |
31 | public class func safeAreaRight() -> CGFloat {
32 | let window = getFirstSharedAppsWindow()
33 | let rightPadding = window?.safeAreaInsets.right ?? 0.0
34 | return rightPadding
35 | }
36 |
37 | }
38 |
39 | private extension UIApplication {
40 |
41 | private class func getFirstSharedAppsWindow() -> UIWindow? {
42 | return UIApplication.shared.keyWindow ?? UIApplication.shared.windows.first
43 | }
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIBezierPath/UIBezierPath+Convenience.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIBezierPath+Convenience.swift
3 | // extensions_kit
4 | //
5 | // Created by Astemir Eleev on 09/05/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import CoreGraphics
10 | import class UIKit.UIBezierPath
11 |
12 | public extension UIBezierPath {
13 |
14 | // MARK: - Properties
15 |
16 | var center: CGPoint {
17 | return CGPoint(x: bounds.midX, y: bounds.midY)
18 | }
19 |
20 | var points: [CGPoint] {
21 | var points = [CGPoint]()
22 |
23 | withUnsafeMutablePointer(to: &points) { pointsPointer in
24 | cgPath.apply(info: pointsPointer) { userInfo, nextElementPointer in
25 | let element = nextElementPointer.pointee
26 | var point = CGPoint.zero
27 | switch element.type {
28 | case .moveToPoint: point = element.points[0]
29 | case .addLineToPoint: point = element.points[0]
30 | default: break
31 | }
32 | let elementsPointer = userInfo!.assumingMemoryBound(to: [CGPoint].self)
33 | elementsPointer.pointee.append(point)
34 | }
35 | }
36 | return points
37 | }
38 |
39 | // MARK: - Initializers
40 |
41 | convenience init(ovalOf size: CGSize) {
42 | self.init(ovalIn: CGRect(origin: CGPoint(x: -size.width / 2, y: -size.height / 2), size: size))
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIBezierPath/UIBezierPath+RandomPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIBezierPath+RandomPoint.swift
3 | // extensions_kit
4 | //
5 | // Created by Astemir Eleev on 09/05/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import CoreGraphics
10 | import class UIKit.UIBezierPath
11 |
12 | extension UIBezierPath {
13 |
14 | func randomPoint(inset: CGFloat) -> CGPoint {
15 | var position: CGPoint!
16 | repeat {
17 | let x = CGFloat.random(in: bounds.origin.x + inset ... bounds.origin.x + bounds.size.width - inset)
18 | let y = CGFloat.random(in: bounds.origin.y + inset ... bounds.origin.y + bounds.size.height - inset)
19 | position = CGPoint(x: x, y: y)
20 | } while (!contains(position))
21 | return position
22 | }
23 |
24 | func randomPointNormalized(inset: CGFloat) -> CGPoint {
25 | let point = randomPoint(inset: inset)
26 | return CGPoint(x: point.x - bounds.origin.x - bounds.size.width / 2, y: point.y - bounds.origin.y - bounds.size.height / 2)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UICollectionView/UICollectionView+CustomCellRegistration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionView+CustomCellRegistration.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 31/05/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UICollectionView {
12 |
13 | /// Registers custom UICollectionViewCell for a UICollectionView instance. UICollectionViewCell needs to be located in current Bundle
14 | ///
15 | /// Usage example:
16 | /// let collectionView = UICollectionView()
17 | /// collectionView.register(cellNamed: UICollectionViewCell.self, reuseableIdentifier: "item-cell")
18 | ///
19 | /// - Parameters:
20 | /// - name: is a name of a UICollectionView that is going to be registered (hint: use UICollectionView.self as a parameter in order to avoid any misspellings)
21 | /// - id: is a reusable identifier for a UICollectionView cell
22 | public func register(cell name: UICollectionViewCell.Type, reusableId id: String) {
23 | let nibName = String(describing: name)
24 | let bundle = Bundle(identifier: nibName)
25 | let cellNib = UINib(nibName: nibName, bundle: bundle)
26 | self.register(cellNib, forCellWithReuseIdentifier: id)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UICollectionView/UICollectionView+Operations.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionView+Operations.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 04/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UICollectionView {
12 |
13 | // MARK: - Typealiases
14 |
15 | public typealias UICollectionViewCompletion = (Bool) -> ()
16 |
17 | // MARK: - Methods
18 |
19 | /// Deletes items from the collection view in the specified section
20 | public func delete(indices: [Int], in section: Int = 0, completion: @escaping UICollectionViewCompletion = { _ in }) {
21 | guard !indices.isEmpty else { return }
22 |
23 | let indexPaths = indices.map { IndexPath(row: $0, section: section) }
24 | performBatchUpdates({ self.deleteItems(at: indexPaths) }, completion: completion)
25 | }
26 |
27 | /// Inserts new items to the collection view to the specified section
28 | public func insert(indices: [Int], in section: Int = 0, completion: @escaping UICollectionViewCompletion = { _ in }) {
29 | guard !indices.isEmpty else { return }
30 |
31 | let indexPaths = indices.map { IndexPath(row: $0, section: section) }
32 | performBatchUpdates({ self.insertItems(at: indexPaths) } , completion: completion)
33 | }
34 |
35 | /// Reloads items in the collection view
36 | public func reload(indices: [Int], in section: Int = 0, completion: @escaping UICollectionViewCompletion = { _ in }) {
37 | guard !indices.isEmpty else { return }
38 |
39 | let indexPaths = indices.map { IndexPath(row: $0, section: section) }
40 | performBatchUpdates({ self.reloadItems(at: indexPaths) } , completion: completion)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UICollectionView/UICollectionView+Safety.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionView+Safety.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 03/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UICollectionView {
12 |
13 | /// Checks if the index path is valid
14 | ///
15 | /// - Parameter indexPath: is an IndexPath to check for
16 | /// - Returns: a boolean value that indicates whether the specified IndexPath is valid or not
17 | public func isValid(indexPath: IndexPath) -> Bool {
18 | return indexPath.section < numberOfSections && indexPath.row < numberOfItems(inSection: indexPath.section)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UICollectionView/UICollectionView+ScrollingUtils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionView+ScrollingUtils.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 03/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UICollectionView {
12 |
13 | /// Scrolls to the bottom of the UICollectionView
14 | ///
15 | /// - Parameters:
16 | /// - animated: is a boolean parameter indicating whether the scrolling should be performed with animation
17 | public func scrollToBottom(animated: Bool = true) {
18 | let bottomOffset = CGPoint(x: 0, y: contentSize.height - bounds.size.height)
19 | setContentOffset(bottomOffset, animated: animated)
20 | }
21 |
22 | /// Scrolls to the top of the UICollectionView
23 | ///
24 | /// - Parameters:
25 | /// - animated: is a boolean parameter indicating wherher the scrolling should be performed with animation
26 | public func scrollToTop(animated: Bool = true) {
27 | setContentOffset(.zero, animated: animated)
28 | }
29 |
30 | /// Wrapper that safely scrolls to the speficied index path
31 | ///
32 | /// - Parameters:
33 | /// - indexPath: is an IndexPath type that specifies the target scrolling position
34 | /// - scrollPosition: is the target scroll position of the specified index path
35 | /// - animated: is a boolean value indicating whether the scrolling should be animated or not
36 | public func safeScrollingToCell(at indexPath: IndexPath, at scrollPosition: UICollectionView.ScrollPosition, animated: Bool = true) {
37 | guard indexPath.section < numberOfSections, indexPath.row < numberOfItems(inSection: indexPath.section) else { return }
38 | scrollToItem(at: indexPath, at: scrollPosition, animated: animated)
39 | }
40 | }
41 |
42 |
43 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIColor/UIColor+Blend.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+Blend.swift
3 | // extensions_kit
4 | //
5 | // Created by Astemir Eleev on 09/05/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import class UIKit.UIColor
10 | import struct CoreGraphics.CGFloat
11 |
12 | public extension UIColor {
13 |
14 | func blend(with color: UIColor, intensity: CGFloat) -> UIColor {
15 | let l1 = intensity
16 | let l2 = 1 - intensity
17 | guard l1 > 0 else { return color}
18 | guard l2 > 0 else { return self}
19 | var (r1, g1, b1, a1): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
20 | var (r2, g2, b2, a2): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
21 |
22 | getRed(&r1, green: &g1, blue: &b1, alpha: &a1)
23 | color.getRed(&r2, green: &g2, blue: &b2, alpha: &a2)
24 |
25 | return UIColor(red: l1 * r1 + l2 * r2, green: l1 * g1 + l2 * g2, blue: l1 * b1 + l2 * b2, alpha: l1 * a1 + l2 * a2)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIColor/UIColor+Brightness.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+Brightness.swift
3 | // extensions_kit
4 | //
5 | // Created by Astemir Eleev on 09/05/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import class UIKit.UIColor
10 | import struct CoreGraphics.CGFloat
11 |
12 | public extension UIColor {
13 |
14 | func increaseBrightness(_ increase: CGFloat) -> UIColor {
15 | var (h, s, b, a): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
16 | getHue(&h, saturation: &s, brightness: &b, alpha: &a)
17 | return UIColor(hue: h, saturation: s, brightness: max(0, min(b + increase, 1)), alpha: a)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIColor/UIColor+ColorComponents.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+ColorComponents.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 06/08/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | // MARK: - The extension adds support for missing color components properties for various use cases
12 | extension UIColor {
13 |
14 | // MARK: - Properties
15 |
16 | public var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
17 | var red: CGFloat = 0
18 | var green: CGFloat = 0
19 | var blue: CGFloat = 0
20 | var alpha: CGFloat = 0
21 |
22 | getRed(&red, green: &green, blue: &blue, alpha: &alpha)
23 | return (red, green, blue, alpha)
24 |
25 | }
26 |
27 | public var hsba: (hue: CGFloat, saturation: CGFloat, brightness: CGFloat, alpha: CGFloat) {
28 | var hue: CGFloat = 0.0
29 | var saturation: CGFloat = 0.0
30 | var brightness: CGFloat = 0.0
31 | var alpha: CGFloat = 0.0
32 |
33 | getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
34 | return (hue, saturation, brightness, alpha)
35 | }
36 |
37 | public var grayscale: (white: CGFloat, alpha: CGFloat) {
38 | var (white, alpha) = (CGFloat(0.0), CGFloat(0.0))
39 |
40 | getWhite(&white, alpha: &alpha)
41 | return (white, alpha)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIImage/UIImage+Downsample.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+Downsample.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 11/01/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIImage {
12 |
13 | /// Downsamples the input image to the specified point size and scale factor. Can be used to present the thumbnails, supports caching. The best way to utilize this method is to use GCD in order to delegate the work off the main thread, then use the .main dispatch queue to sync the results.
14 | ///
15 | /// There is a possible caveat when using this method intensively by repeatedly creating dispatch queues. That may lead to Thread Explosion. If you need to process many images, for instances for `UITableView` or `UICollectionView`, please use a serial dispatch queue and asynchronously delegate the work. That will prevent your downsampling code to cause Thread Explosion and will keep the performance at a decent level.
16 | ///
17 | /// - Parameters:
18 | /// - url: a URL instnace that that specifies the location of an image
19 | /// - pointSize: is the target size represented by `CGSize` that you wish to get
20 | /// - scale: is the scale factor that is represented by `CGFloat`
21 | /// - Returns: an optional `UIImage` instnace that holds the downsampled image, or `nil` if either `CGImageSourceCreateWithURL` or `CGImageSourceCreateThumbnailAtIndex` has failed a `guard` statement
22 | public static func downsample(imageAt url: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage? {
23 | let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
24 | guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, imageSourceOptions) else { return nil }
25 |
26 | let maxDimensionsInPixels = max(pointSize.width, pointSize.height) * scale
27 | let downsampleOptions = [
28 | kCGImageSourceCreateThumbnailFromImageAlways : true,
29 | kCGImageSourceShouldCacheImmediately : true,
30 | kCGImageSourceCreateThumbnailWithTransform : true,
31 | kCGImageSourceThumbnailMaxPixelSize : maxDimensionsInPixels
32 | ] as CFDictionary
33 |
34 | guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else { return nil }
35 |
36 | return .init(cgImage: downsampledImage)
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIImage/UIImage+ImageFromUIView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+ImageFromUIView.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 05/06/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit.UIImage
10 | import UIKit.UIView
11 |
12 | extension UIImage {
13 |
14 | public class func image(from view: UIView) -> UIImage? {
15 | UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0.0)
16 | guard let context = UIGraphicsGetCurrentContext() else { return nil }
17 |
18 | view.layer.render(in: context)
19 | let img = UIGraphicsGetImageFromCurrentImageContext()
20 | UIGraphicsEndImageContext()
21 | return img
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIImage/UIImage+Inverted.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+Inverted.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 10/09/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIImage {
12 |
13 | /// Inverts self by applying CIColorInvert filter
14 | public var inverted: UIImage? {
15 | guard let ciImage = CIImage(image: self) else {
16 | return nil
17 | }
18 | return UIImage(ciImage: ciImage.applyingFilter("CIColorInvert"))
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIImage/UIImage+LandscapeCameraOrientationFix.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+LandscapeCameraOrientationFix.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 30/04/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit.UIImage
10 |
11 | // MARK: - This extension for UIImage fixes image orientation for cases when the iamge was captured using AVFoundation in landscape interface orientation
12 | extension UIImage {
13 |
14 | func landscapeCameraCaptureOrientationFix(for interfaceOrinetation: UIInterfaceOrientation) -> UIImage? {
15 | var imageOrientation: UIImage.Orientation!
16 | var shouldOrient: Bool = false
17 |
18 | if interfaceOrinetation == .landscapeRight {
19 | imageOrientation = UIImage.Orientation.left
20 | shouldOrient = !shouldOrient
21 | } else if interfaceOrinetation == .landscapeLeft {
22 | imageOrientation = UIImage.Orientation.right
23 | shouldOrient = !shouldOrient
24 | }
25 | if shouldOrient, let cgimage = self.cgImage {
26 | let image = UIImage(cgImage: cgimage, scale: self.scale, orientation: imageOrientation)
27 | return image
28 | }
29 |
30 | return self
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIImage/UIImage+RawOrientation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+RawOrientation.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 30/04/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit.UIImage
10 |
11 | public extension UIImage {
12 |
13 | class func rawOrientation(_ value: UIImage.Orientation) -> Int32? {
14 | switch (value) {
15 | case .up:
16 | return 1
17 | case .down:
18 | return 3
19 | case .left:
20 | return 8
21 | case .right:
22 | return 6
23 | case .upMirrored:
24 | return 2
25 | case .downMirrored:
26 | return 4
27 | case .leftMirrored:
28 | return 5
29 | case .rightMirrored:
30 | return 7
31 | @unknown default:
32 | return nil
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIImage/UIImage+Resize.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+Resize.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 30/04/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit.UIImage
10 |
11 | // MARK: - Class-level extension for UIImage that allow to resize input image based on expected image width or/and height
12 | public extension UIImage {
13 |
14 | class func resize(_ image: UIImage, newWidth: CGFloat) -> UIImage? {
15 |
16 | let scale = newWidth / image.size.width
17 | let newHeight = image.size.height * scale
18 | UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
19 | image.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
20 | let newImage = UIGraphicsGetImageFromCurrentImageContext()
21 | UIGraphicsEndImageContext()
22 |
23 | return newImage
24 | }
25 |
26 | class func resize(_ image: UIImage, newHeight: CGFloat) -> UIImage? {
27 |
28 | let scale = newHeight / image.size.height
29 | let newWidth = image.size.width * scale
30 | UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
31 | image.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
32 | let newImage = UIGraphicsGetImageFromCurrentImageContext()
33 | UIGraphicsEndImageContext()
34 |
35 | return newImage
36 | }
37 |
38 | class func resize(_ image: UIImage, newSize: CGSize) -> UIImage? {
39 |
40 | UIGraphicsBeginImageContext(newSize)
41 | image.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
42 | let newImage = UIGraphicsGetImageFromCurrentImageContext()
43 | UIGraphicsEndImageContext()
44 |
45 | return newImage
46 | }
47 |
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIImage/UIImage+SolidColor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+SolidColor.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 30/04/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit.UIImage
10 |
11 | // MARK: - Extension for creating UIImage from a UIColor
12 | extension UIImage {
13 |
14 | public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
15 | let rect = CGRect(origin: .zero, size: size)
16 | UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
17 | color.setFill()
18 | UIRectFill(rect)
19 | let image = UIGraphicsGetImageFromCurrentImageContext()
20 | UIGraphicsEndImageContext()
21 |
22 | guard let cgImage = image?.cgImage else { return nil }
23 | self.init(cgImage: cgImage)
24 | }
25 |
26 | func isImageWhite() -> Bool? {
27 | guard let whiteImage = UIImage(color: .white, size: self.size), let whiteImageData = whiteImage.jpegData(compressionQuality: 1.0), let imageData = self.jpegData(compressionQuality: 1.0) else {
28 | return nil
29 | }
30 | return whiteImageData == imageData
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIImageView/UIImageView+DownloadFromURL.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImageView+DownloadFromURL.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 01/05/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit.UIImageView
10 |
11 | extension UIImageView {
12 |
13 | func downloadImageFrom(url: String, with defaultImage: UIImage? = nil, completion: @escaping(_ image: UIImage)->Void) {
14 | guard let url = URL(string: url) else { return }
15 | URLSession.shared.dataTask(with: url, completionHandler: { data, response, error -> Void in
16 |
17 | guard let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
18 | let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
19 | let data = data, error == nil,
20 | let image = UIImage(data: data)
21 | else {
22 | if let defaultImage = defaultImage {
23 | completion(defaultImage)
24 | }
25 | return
26 | }
27 |
28 | completion(image)
29 | }).resume()
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIImageView/UIImageView+Masking.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImageView+Masking.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 30/04/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit.UIImageView
10 |
11 | public extension UIImageView {
12 |
13 | func mask(with image: UIImage, targetImageSize: CGSize) {
14 | let maskLayer = CALayer()
15 | maskLayer.contents = image.cgImage
16 | maskLayer.frame = CGRect(x: 0, y: 0, width: targetImageSize.width, height: targetImageSize.height)
17 | self.layer.mask = maskLayer
18 | self.layer.masksToBounds = true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIResponder/UIResponder+ChainDescription.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIResponder+ChainDescription.swift
3 | // ExtendedUIKit
4 | //
5 | // Created by Astemir Eleev on 23/12/2019.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit.UIResponder
10 |
11 | public extension UIResponder {
12 |
13 | func responderChain() -> String {
14 | guard let next = next else {
15 | return String(describing: self)
16 | }
17 | return String(describing: self) + " -> " + next.responderChain()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIScreen/UIScreen+InterfaceOrientation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIScreen+InterfaceOrientation.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 16/11/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit.UIScreen
10 |
11 | extension UIScreen {
12 |
13 | /// Interface orientation for the current UIScreen
14 | public var orientation: UIInterfaceOrientation {
15 | let point = coordinateSpace.convert(CGPoint.zero, to: fixedCoordinateSpace)
16 | switch (point.x, point.y) {
17 | case (0, 0):
18 | return .portrait
19 | case let (x, y) where x != 0 && y != 0:
20 | return .portraitUpsideDown
21 | case let (0, y) where y != 0:
22 | return .landscapeLeft
23 | case let (x, 0) where x != 0:
24 | return .landscapeRight
25 | default:
26 | return .unknown
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UITableView/UITableView+FooterHeaderUtils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITableView+FooterHeaderUtils.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 03/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UITableView {
12 |
13 | /// Removes table footer view
14 | public func removeTableFooterView() {
15 | tableFooterView = nil
16 | }
17 |
18 | /// Removes table header view
19 | public func removeTableHeaderView() {
20 | tableHeaderView = nil
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UITableView/UITableView+Safety.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITableView+Safety.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 03/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UITableView {
12 |
13 | /// Checks if the index path is valid
14 | ///
15 | /// - Parameter indexPath: is an IndexPath to check for
16 | /// - Returns: a boolean value that indicates whether the specified IndexPath is valid or not
17 | public func isValidIndexPath(_ indexPath: IndexPath) -> Bool {
18 | return indexPath.section < self.numberOfSections && indexPath.row < self.numberOfRows(inSection: indexPath.section)
19 | }
20 |
21 | }
22 |
23 |
24 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UITableView/UITableView+ScrollingUtils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITableView+ScrollingUtils.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 03/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UITableView {
12 |
13 | /// Scrolls to bottom of the UITalbeView
14 | ///
15 | /// - Parameters:
16 | /// - animated: is a boolean parameter indicating whether the scrolling should be performed with animation
17 | public func scrollToBottom(animated: Bool = true) {
18 | let bottomOffset = CGPoint(x: 0, y: contentSize.height - bounds.size.height)
19 | setContentOffset(bottomOffset, animated: animated)
20 | }
21 |
22 | /// Scrolls to the top of the UITableView
23 | ///
24 | /// - Parameters:
25 | /// - animation: is a boolean parameter indicating whether the scrolling should be performed with animation
26 | public func scrollToTop(animated: Bool = true) {
27 | setContentOffset(CGPoint.zero, animated: animated)
28 | }
29 |
30 | /// Wrapper that safely scrolls to the speficied index path
31 | ///
32 | /// - Parameters:
33 | /// - indexPath: is an IndexPath type that specifies the target scrolling position
34 | /// - scrollPosition: is the target scroll position of the specified index path
35 | /// - animated: is a boolean value indicating whether the scrolling should be animated or not
36 | public func safeScrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool = true) {
37 | guard indexPath.section < numberOfSections, indexPath.row < numberOfRows(inSection: indexPath.section) else { return }
38 | scrollToRow(at: indexPath, at: scrollPosition, animated: animated)
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIView/UIView+BezierRoundedCorners.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+BezierRoundedCorners.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 03/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @available(iOS, deprecated: 11.0)
12 | extension UIView {
13 |
14 | /// Rounds the corners of self by masking the CALayer with a CAShapeLayer using UIBezierPath
15 | ///
16 | /// - Parameters:
17 | /// - corners: is an array of UIRectCorner types e.g. [.topLeft, .topRight]
18 | /// - radius: is a CGFloat value that describes the radius of the corders
19 | public func round(corners: UIRectCorner, radius: CGFloat) {
20 | let maskPath = UIBezierPath(
21 | roundedRect: bounds,
22 | byRoundingCorners: corners,
23 | cornerRadii: CGSize(width: radius, height: radius))
24 |
25 | let shape = CAShapeLayer()
26 | shape.frame = bounds
27 | shape.path = maskPath.cgPath
28 | layer.mask = shape
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIView/UIView+CACorners.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+CaCorners.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 10/09/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @available(iOS, introduced: 11.0)
12 | public extension UIView {
13 |
14 | /// Wrapps `CACornerMask` for more consistent naming. Serves as a proxy for `CACornerMask` and simplifies naming convension for rounded corners.
15 | ///
16 | /// Rather than doing this:
17 | ///
18 | ///`CACornerMask.layerMinXMinYCorner`
19 | ///
20 | /// you do this:
21 | ///
22 | /// `UICorner.topLeft`
23 | ///
24 | /// The struct provides all the capabilities of the standard API `CACornerMask` through `OptoinSet` protocol.
25 | ///
26 | /// There are `5` different constants that can be chained together: 4 constants for each corner and the 5th one for all corners.
27 | struct UICorner: OptionSet {
28 |
29 | // MARK: - Typealiases
30 |
31 | public typealias RawValue = UInt
32 |
33 | // MARK: - Conformance to OptionSet protocol
34 |
35 | public let rawValue: UInt
36 |
37 | // MARK: - Static properties
38 |
39 | public static let topLeft = UICorner(rawValue: 1 << 0)
40 | public static let topRight = UICorner(rawValue: 1 << 1)
41 | public static let bottomLeft = UICorner(rawValue: 1 << 2)
42 | public static let bottomRight = UICorner(rawValue: 1 << 3)
43 |
44 | public static let all: UICorner = [.topLeft, .topRight, .bottomLeft, .bottomRight]
45 |
46 | // MARK: - Initializers
47 |
48 | public init(rawValue: RawValue) {
49 | self.rawValue = rawValue
50 | }
51 |
52 | // MARK: - Fileprivate methods for internal usage
53 |
54 | fileprivate func convert() -> CACornerMask {
55 | switch self {
56 | case .topLeft:
57 | return CACornerMask.layerMinXMinYCorner
58 | case .topRight:
59 | return CACornerMask.layerMaxXMinYCorner
60 | case .bottomLeft:
61 | return CACornerMask.layerMinXMaxYCorner
62 | case .bottomRight:
63 | return CACornerMask.layerMaxXMaxYCorner
64 | case .all:
65 | return [CACornerMask.layerMinXMinYCorner, CACornerMask.layerMaxXMinYCorner, CACornerMask.layerMinXMaxYCorner, CACornerMask.layerMaxXMaxYCorner]
66 | default:
67 | return CACornerMask(rawValue: rawValue)
68 | }
69 | }
70 |
71 | static fileprivate func encode(cornerMask: CACornerMask) -> UICorner {
72 | return UICorner(rawValue: cornerMask.rawValue)
73 | }
74 | }
75 |
76 | /// Rounds the specified `corners` wit the given `radius`
77 | ///
78 | /// - Parameters:
79 | /// - corners: is a UICorner struct
80 | /// - radius: is a CGFloat argument
81 | func round(corners: UICorner, radius: CGFloat) {
82 | layer.cornerRadius = radius
83 | layer.maskedCorners = corners.convert()
84 | clipsToBounds = true
85 | }
86 |
87 | /// Rounds all the corners with the given radius
88 | func roundAllCorners(with radius: CGFloat) {
89 | round(corners: .all, radius: radius)
90 | }
91 |
92 | /// Returns current corner radius
93 | func getCornerRadius() -> CGFloat {
94 | return layer.cornerRadius
95 | }
96 |
97 | func getRoundedCorners() -> UIView.UICorner {
98 | return UICorner.encode(cornerMask: layer.maskedCorners)
99 | }
100 |
101 | func resetRoundedCorners() {
102 | layer.cornerRadius = 0
103 | layer.maskedCorners = .init(rawValue: 0)
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIView/UIView+Constraints.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Constraints.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 09/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIView {
12 |
13 | public func pinSuperview() {
14 | guard let superview = self.superview else {
15 | return
16 | }
17 | self.translatesAutoresizingMaskIntoConstraints = false
18 | addSuperviewConstraint(constraint: topAnchor.constraint(equalTo: superview.topAnchor))
19 | addSuperviewConstraint(constraint: leftAnchor.constraint(equalTo: superview.leftAnchor))
20 | addSuperviewConstraint(constraint: bottomAnchor.constraint(equalTo: superview.bottomAnchor))
21 | addSuperviewConstraint(constraint: rightAnchor.constraint(equalTo: superview.rightAnchor))
22 | }
23 |
24 | public func addSuperviewConstraint(constraint: NSLayoutConstraint) {
25 | superview?.addConstraint(constraint)
26 | }
27 |
28 | /// Retrieves all constraints
29 | public func getAllConstraints() -> [NSLayoutConstraint] {
30 | // array will contain self and all superviews
31 | var views = [self]
32 |
33 | // get all superviews
34 | var view = self
35 | while let superview = view.superview {
36 | views.append(superview)
37 | view = superview
38 | }
39 |
40 | // transforms views to constraints and filter only those
41 | // constraints that include the view itself
42 | return views.flatMap({ $0.constraints }).filter { constraint in
43 | return constraint.firstItem as? UIView == self ||
44 | constraint.secondItem as? UIView == self
45 | }
46 | }
47 |
48 | public func getHeightConstraint() -> NSLayoutConstraint? {
49 | return getAllConstraints().filter({
50 | ($0.firstAttribute == .height && $0.firstItem as? UIView == self) ||
51 | ($0.secondAttribute == .height && $0.secondItem as? UIView == self)
52 | }).first
53 | }
54 |
55 | public func getWidthConstraint() -> NSLayoutConstraint? {
56 | return getAllConstraints().filter({
57 | ($0.firstAttribute == .width && $0.firstItem as? UIView == self) ||
58 | ($0.secondAttribute == .width && $0.secondItem as? UIView == self)
59 | }).first
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIView/UIView+HuggingPriority.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+HuggingPriority.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 10/09/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIView {
12 |
13 | /// Wrapper for 'setContentHuggingPriority' method with more convenient name
14 | public func setHugging(priority: UILayoutPriority, for axis: NSLayoutConstraint.Axis) {
15 | setContentHuggingPriority(priority, for: axis)
16 | }
17 |
18 | /// Wrapper for 'setContentCompressionResistancePriority' method with more convenient name
19 | public func setCompressionResistance(priority: UILayoutPriority, for axis: NSLayoutConstraint.Axis) {
20 | setContentCompressionResistancePriority(priority, for: axis)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIView/UIView+LayoutAnimation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+LayoutAnimation.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 09/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIView {
12 |
13 | public func animateLayoutBounce(duration: Double = 0.5,
14 | usingSpringWithDamping damping: CGFloat = 0.8,
15 | initialSpringVelocity velocity: CGFloat = 0,
16 | completion: (() -> Void)? = nil) {
17 |
18 | UIView.animate(withDuration: duration,
19 | delay: 0,
20 | usingSpringWithDamping: damping,
21 | initialSpringVelocity: velocity,
22 | options: .curveEaseOut,
23 | animations: {
24 | self.layoutIfNeeded()
25 | }, completion: { _ in
26 | completion?()
27 | })
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIView/UIView+Masking.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Masking.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 03/01/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit.UIView
10 |
11 | extension UIView {
12 |
13 | /// Masks the view with the specified UIRectCorner array and corner radius
14 | public func mask(corners: UIRectCorner = [], with cornerRadius: CGFloat = 6) {
15 | let maskPath = UIBezierPath(roundedRect: bounds,
16 | byRoundingCorners: corners,
17 | cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
18 | let maskLayer = CAShapeLayer()
19 | maskLayer.frame = bounds
20 | maskLayer.path = maskPath.cgPath
21 | layer.mask = maskLayer
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIView/UIView+Screenshot.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Screenshot.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 03/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIView {
12 |
13 | /// Takes a screenshot of self, and returns an optional UIImage instance
14 | public var screenshot: UIImage? {
15 | UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, 0)
16 | defer { UIGraphicsEndImageContext() }
17 | guard let context = UIGraphicsGetCurrentContext() else { return nil }
18 | layer.render(in: context)
19 | return UIGraphicsGetImageFromCurrentImageContext()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIViewController/UIViewController+ChildViewControllers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+ChildViewControllers.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 28/05/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public extension UIViewController {
12 |
13 | // MARK: - Methods
14 |
15 | func add(_ child: UIViewController) {
16 | addChild(child)
17 | view.addSubview(child.view)
18 | child.didMove(toParent: self)
19 | }
20 |
21 | func remove() {
22 | guard parent != nil else {
23 | return
24 | }
25 |
26 | willMove(toParent: nil)
27 | removeFromParent()
28 | view.removeFromSuperview()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIViewController/UIViewController+ContainerViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+ContainerViewController.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 03/02/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public extension UIViewController {
12 |
13 | /// Loads a UIViewController from a Storyboard file
14 | func loadViewController(named name: String, storyboard named: String, bundle: Bundle = .main) -> UIViewController {
15 | let storyboard = UIStoryboard(name: named, bundle: bundle)
16 | let viewController = storyboard.instantiateViewController(withIdentifier: name)
17 | return viewController
18 | }
19 |
20 | /// Adds a generic `T` which is `UIViewController` as a child container view controller to the specified generic `V` which is `UIView`. The T is embedded into `V` as where `V` is used as a container view for the view controller of type `T`
21 | func add(viewController: T, to view: V) {
22 | addChild(viewController)
23 | viewController.view.frame = view.bounds
24 | view.addSubview(viewController.view)
25 | viewController.didMove(toParent: self)
26 | }
27 |
28 | /// Removes the generic `T` which is `UIViewController`
29 | func remove(viewController: T) {
30 | willMove(toParent: nil)
31 | viewController.removeFromParent()
32 | removeFromParent()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIViewController/UIViewController+Storyboard.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+Storyboard.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 29/09/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import ExtendedFoundation
11 |
12 | extension UIViewController {
13 |
14 | // MARK: - Public methods
15 |
16 | /// Instantiates a UIViewController instance from a Storyboard using the UIViewController's name as a reference name of the Storyboard file
17 | ///
18 | /// - bundle: is an optional instance of Bundle class. By default it's nil, which means that the defailt bundle will be used
19 | /// - returns: instantiated UIViewController instance
20 | public class func instantiateController(from bundle: Bundle? = nil) -> Self? {
21 | return instantiateFromStoryboard(from: bundle)
22 | }
23 |
24 | /// Instantiates a UIViewController instance from the specified Storyboard and a String identifier
25 | ///
26 | /// - Returns: instantiated UIViewController instance
27 | public class func instantiateController(from storyboard: UIStoryboard, identifier: String) -> Self {
28 | return instantiate(from: storyboard, identifier: identifier)
29 | }
30 |
31 | /// Instantiates a UIView controller instnace from a Storyboard using class name as an identifier
32 | ///
33 | /// - Returns: instantiated UIViewController instnace
34 | public class func instantiateController(from storyboard: UIStoryboard) -> Self {
35 | return instantiate(from: storyboard, identifier: nameOfClass)
36 | }
37 |
38 | // MARK: - Private methods
39 |
40 | private class func instantiateFromStoryboard(from bundle: Bundle? = nil) -> T? {
41 | let storyboard = UIStoryboard(name: String(describing: self), bundle: bundle)
42 | return storyboard.instantiateInitialViewController() as? T
43 | }
44 |
45 | private class func instantiate(from storyboard: UIStoryboard, identifier: String) -> T {
46 | return storyboard.instantiateViewController(withIdentifier: identifier) as! T
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Sources/ExtendedUIKit/UIWindow/UIWindow+Instantiate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIWindow+Instantiate.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 24/01/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIWindow {
12 |
13 | /// Creates a new `UIWindow` instance with the given root view controller, frame and a `WindowOption` if any
14 | public static func create(with rootViewController: T, frame: CGRect = UIScreen.main.bounds, option: WindowOption = .none) -> UIWindow {
15 | let window = UIWindow(frame: frame)
16 | window.rootViewController = rootViewController
17 | let windowsOptionFunction = option.convert(for: window)
18 | windowsOptionFunction()
19 |
20 | return window
21 | }
22 | }
23 |
24 | /// Is a wrapper enum type that simplifies the instantiation of `UIWindow` class
25 | ///
26 | /// - none: no options applied
27 | /// - key: makes the `UIWindow` instance key but not visible
28 | /// - keyAndVisible: makes the `UIWindow` instnace key and visible
29 | public enum WindowOption {
30 | case none
31 | case key
32 | case keyAndVisible
33 | }
34 |
35 | public extension WindowOption {
36 | func convert(for window: UIWindow) -> () -> Void {
37 | switch self {
38 | case .none:
39 | return { /* empty function that will does nothing or none */ }
40 | case .key:
41 | return window.makeKey
42 | case .keyAndVisible:
43 | return window.makeKeyAndVisible
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/Extendedos/OSLog/OSLog+LogLevels.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OSLog+LogLevels.swift
3 | // extensions-kit
4 | //
5 | // Created by Astemir Eleev on 25/01/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import os
10 | import Foundation
11 |
12 | public enum SubsystemError: Error {
13 | case missingBundleIdentifier(String)
14 | }
15 |
16 | @available(iOS 12.0, *)
17 | extension OSLog {
18 |
19 | // MARK: Static properties
20 |
21 | public static var ui = OSLog(subsystem: try! subsystem(), category: "UI")
22 | public static var network = OSLog(subsystem: try! subsystem(), category: "Network")
23 | public static var `default` = OSLog(subsystem: try! subsystem(), category: "Default")
24 | public static var info = OSLog(subsystem: try! subsystem(), category: "Info")
25 | public static var debug = OSLog(subsystem: try! subsystem(), category: "Debug")
26 | public static var error = OSLog(subsystem: try! subsystem(), category: "Error")
27 | public static var fault = OSLog(subsystem: try! subsystem(), category: "Fault")
28 |
29 | // MARK: - Private helpers
30 |
31 | private static func subsystem() throws -> String {
32 | guard let subsystemIdentifier = Bundle.main.bundleIdentifier else { throw SubsystemError.missingBundleIdentifier("Could not get bundle identifier from the main bundle") }
33 | return subsystemIdentifier
34 | }
35 | }
36 |
37 | // MARK: - Global functions
38 |
39 | /// Sends a message to the logging system, optionally specifying a custom log object that specifies the subsystem with log category.
40 | ///
41 | /// The method is a convenience wrapper around `os.os_log` method, but with fewer parameters
42 | public func os_log(_ message: StaticString, log: OSLog) {
43 | os.os_log(message, log: log)
44 | }
45 |
--------------------------------------------------------------------------------
/Tests/ExtendedFoundationTests/Array+ContainsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+ContainsTests.swift
3 | // extensions-kit-tests
4 | //
5 | // Created by Astemir Eleev on 28/12/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ExtendedFoundation
11 |
12 | final class ArrayContainsTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testContains", testContains)
16 | ]
17 |
18 | override func setUp() {
19 | // Put setup code here. This method is called before the invocation of each test method in the class.
20 | }
21 |
22 | override func tearDown() {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func testContains() {
27 | let testArray = [1,2,4,5,6,7,8,9,10]
28 | var result = testArray.contains(elements: 1,2,4,5)
29 | XCTAssert(result == true)
30 |
31 | result = testArray.contains(elements: 9,10,11,12)
32 | XCTAssert(result == false)
33 | }
34 |
35 | func testPerformanceExample() {
36 | // This is an example of a performance test case.
37 | self.measure {
38 | // Put the code you want to measure the time of here.
39 | }
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/Tests/ExtendedFoundationTests/Array+DifferenceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+DifferenceTests.swift
3 | // extensions-kit-tests
4 | //
5 | // Created by Astemir Eleev on 28/12/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ExtendedFoundation
11 |
12 | final class ArrayDifferenceTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testDiff", testDiff)
16 | ]
17 |
18 | override func setUp() {
19 | // Put setup code here. This method is called before the invocation of each test method in the class.
20 | }
21 |
22 | override func tearDown() {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func testDiff() {
27 | let testA = [1,2,3,4,5]
28 | let testB = [4,5,7,8,9]
29 |
30 | var result = testA.difference(elements: testB)
31 | XCTAssert(result == [1,2,3])
32 |
33 | result = testB.difference(elements: testA)
34 | XCTAssert(result == [7,8,9])
35 | }
36 |
37 | func testPerformanceExample() {
38 | // This is an example of a performance test case.
39 | self.measure {
40 | // Put the code you want to measure the time of here.
41 | }
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/Tests/ExtendedFoundationTests/Array+FilterTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+FilterTests.swift
3 | // extensions-kit-tests
4 | //
5 | // Created by Astemir Eleev on 06/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ExtendedFoundation
11 |
12 | final class ArrayFilterTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testSkip", testSkip),
16 | ("testAll", testAll),
17 | ("testAny", testAny)
18 | ]
19 |
20 | let testArrayInt = [1,2,3,4,5,6,7,8,9,10]
21 | let testArrayString = ["a", "b", "c", "d", "e", "f", "g"]
22 |
23 | override func setUp() {
24 | // Put setup code here. This method is called before the invocation of each test method in the class.
25 | }
26 |
27 | override func tearDown() {
28 | // Put teardown code here. This method is called after the invocation of each test method in the class.
29 | }
30 |
31 | func testSkip() {
32 | var result = testArrayInt.skip(5)
33 | XCTAssert(result == [6,7,8,9,10])
34 | result = testArrayInt.skip(2)
35 | XCTAssert(result == [3,4,5,6,7,8,9,10])
36 | result = testArrayInt.skip(10)
37 | XCTAssert(result == [])
38 |
39 | var resultS = testArrayString.skip(5)
40 | XCTAssert(resultS == ["f", "g"])
41 | resultS = testArrayString.skip(2)
42 | XCTAssert(resultS == ["c", "d", "e", "f", "g"])
43 | resultS = testArrayString.skip(10)
44 | XCTAssert(resultS == [])
45 | }
46 |
47 | func testAll() {
48 | var result = testArrayInt.all { $0 < 20 }
49 | XCTAssert(result == true)
50 | result = testArrayInt.all { $0 > -1 }
51 | XCTAssert(result == true)
52 | result = testArrayInt.all { $0 % 2 == 0 }
53 | XCTAssert(result == false)
54 |
55 | var resultS = testArrayString.all { $0.count == 1 }
56 | XCTAssert(resultS == true)
57 | resultS = testArrayString.all { $0.starts(with: "a") }
58 | XCTAssert(resultS == false)
59 | }
60 |
61 | func testAny() {
62 | var result = testArrayInt.any { $0 < 5 }
63 | XCTAssert(result == true)
64 | result = testArrayInt.any { $0 > 10 }
65 | XCTAssert(result == false)
66 | result = testArrayInt.any { $0 % 2 == 0 }
67 | XCTAssert(result == true)
68 |
69 | var resultS = testArrayString.any { $0.count == 1 }
70 | XCTAssert(resultS == true)
71 | resultS = testArrayString.any { $0.starts(with: "a") }
72 | XCTAssert(resultS == true)
73 | }
74 |
75 | func testPerformanceExample() {
76 | // This is an example of a performance test case.
77 | self.measure {
78 | // Put the code you want to measure the time of here.
79 | }
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/Tests/ExtendedFoundationTests/Array+IntersectionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+IntersectionTests.swift
3 | // extensions-kit-tests
4 | //
5 | // Created by Astemir Eleev on 28/12/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ExtendedFoundation
11 |
12 | final class ArrayIntersectionTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testIntersection", testIntersection)
16 | ]
17 |
18 | override func setUp() {
19 | // Put setup code here. This method is called before the invocation of each test method in the class.
20 | }
21 |
22 | override func tearDown() {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func testIntersection() {
27 | let testA = [1,2,3,4,5]
28 | let testB = [4,5,6,7]
29 |
30 | var result = testA.intersection(values: testB)
31 | XCTAssert(result == [4,5])
32 |
33 | result = testB.intersection(values: testA)
34 | XCTAssert(result == [4,5])
35 |
36 | let wrongTestB = [6,7,8,9]
37 | result = testA.intersection(values: wrongTestB)
38 | XCTAssert(result == [])
39 | }
40 |
41 | func testPerformanceExample() {
42 | // This is an example of a performance test case.
43 | self.measure {
44 | // Put the code you want to measure the time of here.
45 | }
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/Tests/ExtendedFoundationTests/Array+RemoveTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+RemoveTests.swift
3 | // extensions-kit-tests
4 | //
5 | // Created by Astemir Eleev on 28/12/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ExtendedFoundation
11 |
12 | final class ArrayRemoveTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testRemove", testRemove)
16 | ]
17 |
18 | override func setUp() {
19 | // Put setup code here. This method is called before the invocation of each test method in the class.
20 | }
21 |
22 | override func tearDown() {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func testRemove() {
27 | var test = [1,2,3,4,5,6,7,8]
28 |
29 | test.remove(object: 8)
30 | XCTAssert(test == [1,2,3,4,5,6,7])
31 |
32 | test.remove(objects: [1,2,4])
33 | XCTAssert(test == [3,5,6,7])
34 |
35 | test.remove(objects: 5,6)
36 | XCTAssert(test == [3,7])
37 | }
38 |
39 | func testPerformanceExample() {
40 | // This is an example of a performance test case.
41 | self.measure {
42 | // Put the code you want to measure the time of here.
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/Tests/ExtendedFoundationTests/Array+UnionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+UnionTests.swift
3 | // extensions-kit-tests
4 | //
5 | // Created by Astemir Eleev on 06/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ExtendedFoundation
11 |
12 | final class ArrayUnionTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testUnion", testUnion)
16 | ]
17 |
18 | override func setUp() {
19 | // Put setup code here. This method is called before the invocation of each test method in the class.
20 | }
21 |
22 | override func tearDown() {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func testUnion() {
27 | let firstArray = [1,2,4,5,6,7,8]
28 | let secondArry = [8,9,10]
29 |
30 | let unionedIntArray = firstArray.union(values: secondArry)
31 | XCTAssert(unionedIntArray == [1, 2, 4, 5, 6, 7, 8, 9, 10])
32 | }
33 |
34 | func testPerformanceExample() {
35 | // This is an example of a performance test case.
36 | self.measure {
37 | // Put the code you want to measure the time of here.
38 | }
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/Tests/ExtendedFoundationTests/BoolTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BoolTests.swift
3 | // extensions-kit-tests
4 | //
5 | // Created by Astemir Eleev on 14/08/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ExtendedFoundation
11 |
12 | class BoolTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testInt", testInt)
16 | ]
17 |
18 | override func setUp() {
19 | super.setUp()
20 | // Put setup code here. This method is called before the invocation of each test method in the class.
21 | }
22 |
23 | override func tearDown() {
24 | // Put teardown code here. This method is called after the invocation of each test method in the class.
25 | super.tearDown()
26 | }
27 |
28 | func testInt() {
29 | let falseBool = false
30 | XCTAssert(falseBool.int == 0)
31 |
32 | let trueBool = true
33 | XCTAssert(trueBool.int == 1)
34 | }
35 |
36 | func testPerformanceExample() {
37 | // This is an example of a performance test case.
38 | self.measure {
39 | // Put the code you want to measure the time of here.
40 | }
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/Tests/ExtendedFoundationTests/DecodableTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DecodableTests.swift
3 | // extensions-kit-tests
4 | //
5 | // Created by Astemir Eleev on 24/12/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ExtendedFoundation
11 |
12 | class DecodableTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testFileDecoding", testFileDecoding)
16 | ]
17 |
18 | override func setUp() {
19 | // Put setup code here. This method is called before the invocation of each test method in the class.
20 | }
21 |
22 | override func tearDown() {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func testFileDecoding() {
27 | // // We need this type of bundle in order to be able to "reach" the .json file inside the test target folder
28 | // let bundle = Bundle(for: type(of: self))
29 | //
30 | // guard let decodedUser = try? User.decodeFromFile(named: "User", in: bundle) else {
31 | // XCTAssert(false)
32 | // return
33 | // }
34 | // print("decoded user: ", decodedUser)
35 | //
36 | // XCTAssert(decodedUser.name == "Willy")
37 | // XCTAssert(decodedUser.age == 30)
38 | }
39 |
40 | func testPerformanceExample() {
41 | // This is an example of a performance test case.
42 | self.measure {
43 | // Put the code you want to measure the time of here.
44 | }
45 | }
46 |
47 | }
48 |
49 |
50 | struct User: Codable {
51 | var name: String
52 | var age: Int
53 | }
54 |
--------------------------------------------------------------------------------
/Tests/ExtendedFoundationTests/DictionaryTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DictionaryTests.swift
3 | // extensions-kit-tests
4 | //
5 | // Created by Astemir Eleev on 16/08/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ExtendedFoundation
11 |
12 | class DictionaryTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testJsonData", testJsonData),
16 | ("testJsonString", testJsonString)
17 | ]
18 |
19 | override func setUp() {
20 | super.setUp()
21 | // Put setup code here. This method is called before the invocation of each test method in the class.
22 | }
23 |
24 | override func tearDown() {
25 | // Put teardown code here. This method is called after the invocation of each test method in the class.
26 | super.tearDown()
27 | }
28 |
29 | func testJsonData() {
30 | let data: [String: String] = ["1" : "Hello JSON!", "2" : "This is the second value!", "3" : "And the third one"]
31 |
32 | guard let jsonData = data.jsonData() else {
33 | XCTAssert(false, "Could not unwrap the Data?")
34 | return
35 | }
36 | do {
37 | let decodedData = try JSONSerialization.jsonObject(with: jsonData, options: [])
38 |
39 | if let udecodedData = decodedData as? [String : String] {
40 | if udecodedData == data {
41 |
42 | // Success
43 | XCTAssert(true)
44 | } else {
45 | // Failed
46 | XCTAssert(false, "The original data dict is not equal to the unwrapped one")
47 | }
48 | } else {
49 | XCTAssert(false, "Could not case Any JSON data to expected [String:String] dictionary")
50 | }
51 |
52 | } catch {
53 | XCTAssert(false, "JSON serualization raised an error: \(error)")
54 | }
55 | }
56 |
57 | func testJsonString() {
58 | let data: [String: String] = ["1" : "Hello JSON!", "2" : "This is the second value!", "3" : "And the third one"]
59 |
60 | guard let jsonString = data.jsonString() else {
61 | XCTAssert(false, "Could not unwrap the String?")
62 | return
63 | }
64 |
65 | guard let jsonObject = jsonString.data(using: .utf8) else {
66 | XCTAssert(false, "Could not unwrap the Data?")
67 | return
68 | }
69 |
70 | do {
71 | let decodedData = try JSONSerialization.jsonObject(with: jsonObject, options: [])
72 |
73 | if let udecodedData = decodedData as? [String : String] {
74 | if udecodedData == data {
75 |
76 | // Success
77 | XCTAssert(true)
78 | } else {
79 | // Failed
80 | XCTAssert(false, "The original data dict is not equal to the unwrapped one")
81 | }
82 | } else {
83 | XCTAssert(false, "Could not case Any JSON data to expected [String:String] dictionary")
84 | }
85 |
86 | } catch {
87 | XCTAssert(false, "JSON serualization raised an error: \(error)")
88 | }
89 | }
90 |
91 | func testPerformanceExample() {
92 | // This is an example of a performance test case.
93 | self.measure {
94 | // Put the code you want to measure the time of here.
95 | }
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/Tests/ExtendedFoundationTests/DoubleTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DoubleTests.swift
3 | // extensions-kit-tests
4 | //
5 | // Created by Astemir Eleev on 27/08/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ExtendedFoundation
11 |
12 | class DoubleTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testCurrecyShortcuts", testCurrecyShortcuts),
16 | ("testRounded", testRounded)
17 | ]
18 |
19 | override func setUp() {
20 | super.setUp()
21 | // Put setup code here. This method is called before the invocation of each test method in the class.
22 | }
23 |
24 | override func tearDown() {
25 | // Put teardown code here. This method is called after the invocation of each test method in the class.
26 | super.tearDown()
27 | }
28 |
29 | func testCurrecyShortcuts() {
30 | let usd = 350.00.usd
31 | XCTAssert(usd == "$ 350.00")
32 |
33 | let usdDecimal = 450.34.usd
34 | XCTAssert(usdDecimal == "$ 450.34")
35 |
36 | let rub = 120.00.ruble
37 | XCTAssert(rub == "120,00 ₽")
38 | }
39 |
40 | func testRounded() {
41 | let rounded3 = 23.3434.rounded(toPlaces: 3)
42 | XCTAssert(rounded3 == 23.343)
43 |
44 | let rounded2 = 3.527.rounded(toPlaces: 2)
45 | XCTAssert(rounded2 == 3.53)
46 |
47 | let rounded1 = 7.32.rounded(toPlaces: 1)
48 | XCTAssert(rounded1 == 7.3)
49 | }
50 |
51 | func testPerformanceExample() {
52 | // This is an example of a performance test case.
53 | self.measure {
54 | // Put the code you want to measure the time of here.
55 | }
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/Tests/ExtendedFoundationTests/IntDecimalToBinaryTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IntDecimalToBinaryTests.swift
3 | // extensions-kit-tests
4 | //
5 | // Created by Astemir Eleev on 09/10/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ExtendedFoundation
11 |
12 | class IntDecimalToBinaryTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testDecimalToBinary", testDecimalToBinary),
16 | ("testBinaryToDecimal", testBinaryToDecimal)
17 | ]
18 |
19 | override func setUp() {
20 | // Put setup code here. This method is called before the invocation of each test method in the class.
21 | }
22 |
23 | override func tearDown() {
24 | // Put teardown code here. This method is called after the invocation of each test method in the class.
25 | }
26 |
27 | func testDecimalToBinary() {
28 | // Decimal Binary
29 | // 0 0000
30 | // 1 0001
31 | // 2 0010
32 | // 3 0011
33 | // 4 0100
34 | // 5 0101
35 | // 6 0110
36 | // 7 0111
37 | // 8 1000
38 | // 9 1001
39 | // 10 1010
40 | // 11 1011
41 | // 12 1100
42 | // 13 1101
43 | // 14 1110
44 | // 15 1111
45 |
46 | let binary1 = 1.decimalToBinary()
47 | let binary2 = 2.decimalToBinary()
48 | let binary3 = 3.decimalToBinary()
49 | let binary4 = 4.decimalToBinary()
50 | let binary5 = 5.decimalToBinary()
51 | let binary6 = 6.decimalToBinary()
52 | let binary7 = 7.decimalToBinary()
53 | let binary8 = 8.decimalToBinary()
54 | let binary9 = 9.decimalToBinary()
55 | let binary10 = 10.decimalToBinary()
56 | let binary11 = 11.decimalToBinary()
57 | let binary12 = 12.decimalToBinary()
58 | let binary13 = 13.decimalToBinary()
59 | let binary14 = 14.decimalToBinary()
60 | let binary15 = 15.decimalToBinary()
61 |
62 | XCTAssert(binary1 == 1)
63 | XCTAssert(binary2 == 10)
64 | XCTAssert(binary3 == 11)
65 | XCTAssert(binary4 == 100)
66 | XCTAssert(binary5 == 101)
67 | XCTAssert(binary6 == 110)
68 | XCTAssert(binary7 == 111)
69 | XCTAssert(binary8 == 1000)
70 | XCTAssert(binary9 == 1001)
71 | XCTAssert(binary10 == 1010)
72 | XCTAssert(binary11 == 1011)
73 | XCTAssert(binary12 == 1100)
74 | XCTAssert(binary13 == 1101)
75 | XCTAssert(binary14 == 1110)
76 | XCTAssert(binary15 == 1111)
77 | }
78 |
79 | func testBinaryToDecimal() {
80 | let decimal1 = 1.binaryToDecimal()
81 | let decimal2 = 10.binaryToDecimal()
82 | let decimal3 = 11.binaryToDecimal()
83 | let decimal4 = 100.binaryToDecimal()
84 | let decimal5 = 101.binaryToDecimal()
85 | let decimal6 = 110.binaryToDecimal()
86 | let decimal7 = 111.binaryToDecimal()
87 |
88 | XCTAssert(decimal1 == 1)
89 | XCTAssert(decimal2 == 2)
90 | XCTAssert(decimal3 == 3)
91 | XCTAssert(decimal4 == 4)
92 | XCTAssert(decimal5 == 5)
93 | XCTAssert(decimal6 == 6)
94 | XCTAssert(decimal7 == 7)
95 | }
96 |
97 | func testPerformanceExample() {
98 | // This is an example of a performance test case.
99 | self.measure {
100 | // Put the code you want to measure the time of here.
101 | }
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/Tests/ExtendedFoundationTests/NSObjectTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSObjectTests.swift
3 | // extensions-kit-tests
4 | //
5 | // Created by Astemir Eleev on 02/01/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ExtendedFoundation
11 |
12 | class NSObjectTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testObjectName", testObjectName)
16 | ]
17 |
18 | override func setUp() {
19 | // Put setup code here. This method is called before the invocation of each test method in the class.
20 | }
21 |
22 | override func tearDown() {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func testObjectName() {
27 | let _ = Foo()
28 | let _ = Bar()
29 |
30 | let fooClassName = Foo.nameOfClass
31 | let barClassName = Bar.nameOfClass
32 |
33 | debugPrint("foo : ", fooClassName)
34 | debugPrint("bar : ", barClassName)
35 | XCTAssert(fooClassName == "Foo")
36 | XCTAssert(barClassName == "Bar")
37 | }
38 |
39 | func testPerformanceExample() {
40 | // This is an example of a performance test case.
41 | self.measure {
42 | // Put the code you want to measure the time of here.
43 | }
44 | }
45 |
46 | }
47 |
48 | class Foo: NSObject { }
49 | class Bar: NSObject { }
50 |
--------------------------------------------------------------------------------
/Tests/ExtendedFoundationTests/OptionSetTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OptionSetTests.swift
3 | // extensions-kit-tests
4 | //
5 | // Created by Astemir Eleev on 29/08/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ExtendedFoundation
11 |
12 | class OptionSetTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testOperations", testOperations)
16 | ]
17 |
18 | override func setUp() {
19 | super.setUp()
20 | // Put setup code here. This method is called before the invocation of each test method in the class.
21 | }
22 |
23 | override func tearDown() {
24 | // Put teardown code here. This method is called after the invocation of each test method in the class.
25 | super.tearDown()
26 | }
27 |
28 | func testOperations() {
29 | let caseInsensitive = String.CompareOptions.caseInsensitive
30 |
31 | let backwards = String.CompareOptions.backwards
32 | let caseInsensitivebackwards = caseInsensitive.inserting(new: backwards)
33 |
34 | let result = String.CompareOptions(rawValue: caseInsensitivebackwards.rawValue)
35 |
36 | XCTAssertTrue(caseInsensitivebackwards == result)
37 |
38 |
39 | let removedCaseInsensitive = backwards.removing(caseInsensitive)
40 | let removedResult = String.CompareOptions(rawValue: removedCaseInsensitive.rawValue)
41 |
42 | XCTAssertTrue(removedResult == backwards)
43 | }
44 |
45 | func testPerformanceExample() {
46 | // This is an example of a performance test case.
47 | self.measure {
48 | // Put the code you want to measure the time of here.
49 | }
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/Tests/ExtendedFoundationTests/SequenceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SequenceTests.swift
3 | // extensions-kit-tests
4 | //
5 | // Created by Astemir Eleev on 14/08/2018.
6 | // Copyright © 2018 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ExtendedFoundation
11 |
12 | class SequenceTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testShuffled", testShuffled),
16 | ("testCount", testCount),
17 | ("testDuplicatedRemoved", testDuplicatedRemoved)
18 | ]
19 |
20 | override func setUp() {
21 | super.setUp()
22 | // Put setup code here. This method is called before the invocation of each test method in the class.
23 | }
24 |
25 | override func tearDown() {
26 | // Put teardown code here. This method is called after the invocation of each test method in the class.
27 | super.tearDown()
28 | }
29 |
30 | func testShuffled() {
31 | let array = [1,2,3,4,5,6,7]
32 | var shuffledArray = array.shuffled()
33 | var iterations = 10
34 |
35 | while iterations > -1 {
36 | XCTAssert(shuffledArray != array.shuffled())
37 |
38 | shuffledArray = array.shuffled()
39 | iterations -= 1
40 | }
41 |
42 | }
43 |
44 | func testCount() {
45 | let array = [1,2,3,4,5,6,7,8,9]
46 | let numberOfEvens = array.count {
47 | $0 % 2 == 0
48 | }
49 |
50 | XCTAssert(numberOfEvens == 4)
51 |
52 | let numberOfOdds = array.count {
53 | $0 % 2 == 1
54 | }
55 |
56 | XCTAssert(numberOfOdds == 5)
57 |
58 | let stringArray = ["Asdjf", "ASGhjsndm", "eds", "Hello", "snoyle", "Long", "Play", "vAMpire", "AsdinneR"]
59 |
60 | let prefixCount = stringArray.count {
61 | $0.hasPrefix("As")
62 | }
63 |
64 | XCTAssert(prefixCount == 2)
65 | }
66 |
67 | func testDuplicatedRemoved() {
68 | let test = [1,1,6,4,3,3,4,5,7,7,3,4,5,5,3,2,3,4]
69 |
70 | let removedDuplicates = test.removeDuplicates()
71 |
72 | XCTAssert(removedDuplicates == [1,6,4,3,5,7,2])
73 | }
74 |
75 | func testPerformanceExample() {
76 | // This is an example of a performance test case.
77 | self.measure {
78 | // Put the code you want to measure the time of here.
79 | }
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/Tests/ExtendedFoundationTests/UnicodeOutputStreamTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UnicodeOutputStreamTests.swift
3 | // extensions-kit-tests
4 | //
5 | // Created by Astemir Eleev on 29/01/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ExtendedFoundation
11 |
12 | class UnicodeOutputStreamTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("test", test)
16 | ]
17 |
18 | override func setUp() {
19 | // Put setup code here. This method is called before the invocation of each test method in the class.
20 | }
21 |
22 | override func tearDown() {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func test() {
27 | let tastables = ["0: 👨 U+1F468\tMAN", "1: U+200D\tZERO WIDTH JOINER", "2: 👩 U+1F469\tWOMAN", "3: U+200D\tZERO WIDTH JOINER", "4: 👧 U+1F467\tGIRL", "5: U+200D\tZERO WIDTH JOINER", "6: 👧 U+1F467\tGIRL"]
28 |
29 | var unicodeOutputStream = UnicodeOutputStream()
30 | unicodeOutputStream.outputCompletion = {
31 | equalsAtLeastOne(from: tastables, target: $0)
32 | }
33 | print("👨👩👧👧", to: &unicodeOutputStream)
34 |
35 | func equalsAtLeastOne(from collection: [T], target: T) {
36 | for item in collection where item == target {
37 | XCTAssert(true)
38 | return
39 | }
40 | XCTAssert(false)
41 | }
42 | }
43 |
44 | func testPerformanceExample() {
45 | // This is an example of a performance test case.
46 | self.measure {
47 | // Put the code you want to measure the time of here.
48 | }
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import ExtensionsKitTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += ExtensionsKitTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/Tests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(ArrayContainsTests.allTests),
7 | testCase(ArrayDifferenceTests.allTests),
8 | testCase(ArrayIntersectionTests.allTests),
9 | testCase(ArrayRemoveTests.allTests),
10 | testCase(ArrayFilterTests.allTests),
11 | testCase(ArrayUnionTests.allTests),
12 | testCase(BoolTests.allTests),
13 | testCase(DecodableTests.allTests),
14 | testCase(DictionaryTests.allTests),
15 | testCase(DoubleTests.allTests),
16 | testCase(IntDecimalToBinaryTests.allTests),
17 | testCase(IntTests.allTests),
18 | testCase(NSObjectTests.allTests),
19 | testCase(OptionSetTests.allTests),
20 | testCase(SeqeunceTests.allTests),
21 | testCase(StringTests.allTests),
22 | testCase(UnicodeOutputStreamTests.allTests)
23 | ]
24 | }
25 | #endif
26 |
--------------------------------------------------------------------------------
/logo-extensions_kit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eleev/extensions-kit/b40c80d4b15a4216f446b445aee48a74a5698b64/logo-extensions_kit.png
--------------------------------------------------------------------------------