├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── Bug_report.md │ └── Feature_request.md └── workflows │ └── build.yml ├── .gitignore ├── .swiftpm └── xcode │ ├── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ └── xcschemes │ └── ExtensionsKit.xcscheme ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── ExtendedAVFoundation │ └── AVCaptureDevice+ToggleFlash.swift ├── ExtendedCoreAnimation │ └── CAAnimation │ │ └── CAAnimation+PatternReplicator.swift ├── ExtendedCoreGraphics │ ├── CGFloat │ │ └── CGFloat+Rounded.swift │ ├── CGPoint │ │ ├── CGPoint+Operators.swift │ │ └── CGPoint+Utils.swift │ ├── CGRect │ │ ├── CGRect+AspectFit.swift │ │ ├── CGRect+Corners.swift │ │ ├── CGRect+Mid.swift │ │ └── CGRect+Scale.swift │ └── CGSize │ │ └── CGSize+Operators.swift ├── ExtendedCoreImage │ ├── CIImage+Inverted.swift │ ├── CIImage+QRImage.swift │ ├── CIImage+Tinted.swift │ ├── CIImage+Transparent.swift │ └── Filters │ │ └── HighlightFilter.swift ├── ExtendedFoundation │ ├── Array │ │ ├── Array+Contains.swift │ │ ├── Array+Difference.swift │ │ ├── Array+Filtering.swift │ │ ├── Array+Intersection.swift │ │ ├── Array+Remove.swift │ │ └── Array+Union.swift │ ├── Bool │ │ ├── Bool+Int.swift │ │ └── Bool+Random.swift │ ├── ClosedRange │ │ └── ClosedRange+Random.swift │ ├── Collection │ │ ├── Collection+ParallelIteration.swift │ │ ├── Collection+RandomItem.swift │ │ ├── Collection+SafeSubscript.swift │ │ └── Collection+Sum&Average.swift │ ├── Custom TextOutputStream │ │ ├── FileOutputStream.swift │ │ └── UnicodeOutputStream.swift │ ├── CustomProtocols │ │ ├── Identifiable.swift │ │ └── Then.swift │ ├── CustomTypes │ │ ├── Debouncer.swift │ │ ├── ObservableArray.swift │ │ └── Variable.swift │ ├── Data │ │ ├── Data+Bytes.swift │ │ ├── Data+Matches.swift │ │ └── Data+MimeType.swift │ ├── Date │ │ ├── Date+FirstLast.swift │ │ └── Date+PreviousNext.swift │ ├── Decodable │ │ └── Decodable+DecodeFromFile.swift │ ├── Dictionary │ │ ├── Dictionary+ConvenienceWrappers.swift │ │ ├── Dictionary+Difference.swift │ │ ├── Dictionary+GetOrAddValue.swift │ │ ├── Dictionary+Intersection.swift │ │ ├── Dictionary+JSON.swift │ │ ├── Dictionary+Map.swift │ │ └── Dictionary+Union.swift │ ├── Double │ │ ├── Double+CurrencyShortcuts.swift │ │ └── Double+Rounded.swift │ ├── Float │ │ └── Float+Rounded.swift │ ├── Functions │ │ └── FunctionalComposition.swift │ ├── Int │ │ ├── Int+Clamp.swift │ │ ├── Int+DecimalToBinary.swift │ │ ├── Int+Digits.swift │ │ ├── Int+EvenOdd.swift │ │ ├── Int+Factorial.swift │ │ ├── Int+Power.swift │ │ ├── Int+Random.swift │ │ └── Int+Roman.swift │ ├── MIrror │ │ └── MIrror+ReflectedProperties.swift │ ├── MutableCollection │ │ └── MutableCollection+Shuffle.swift │ ├── NSObject │ │ └── NSObject+ClassName.swift │ ├── NSObjectProtocol │ │ └── NSObjectProtocol+KVO+KVC.swift │ ├── NotificationCenter │ │ └── NotificationCenter+PostUtils.swift │ ├── Operaton │ │ └── OperationQueue+MainUtils.swift │ ├── OptionSet │ │ └── OptionSet+Operations.swift │ ├── RandomAccessCollection │ │ └── RandomAccessCollection+BinarySearch.swift │ ├── Sequence │ │ ├── Sequence+Count.swift │ │ ├── Sequence+DuplicatesRemoved.swift │ │ └── Sequence+Shuffle.swift │ ├── String │ │ ├── String+Base64.swift │ │ ├── String+Digits.swift │ │ ├── String+FormattedDate.swift │ │ ├── String+IndexOf.swift │ │ ├── String+Subscript.swift │ │ └── String+Validation.swift │ └── URL │ │ └── URL+QRImage.swift ├── ExtendedFoundationDataStructures │ ├── Builder │ │ └── BuilderProtocol.swift │ ├── Dequeue │ │ └── Dequeue.swift │ ├── DoublyLinkedList │ │ ├── DoublyLinkedList.swift │ │ └── DoublyNode.swift │ ├── FunctionalLenses │ │ └── Lens.swift │ ├── Heap │ │ └── Heap.swift │ ├── LinkedList │ │ └── LinkedList.swift │ ├── MulticastDelegation │ │ └── MulticastDelegation.swift │ ├── ObjectPool │ │ ├── ObjectPool.swift │ │ └── ObjectPoolItem.swift │ ├── Observer │ │ ├── Notification.swift │ │ ├── Observer.swift │ │ └── Subject.swift │ ├── PriorityQueue │ │ └── PriorityQueue.swift │ ├── Queue │ │ └── Queue.swift │ └── Stack │ │ └── Stack.swift ├── ExtendedFoundationSorting │ ├── Array+BubbleSort.swift │ ├── Array+InsertionSort.swift │ ├── Array+MergeSort.swift │ ├── Array+QuickSortHoareScheme.swift │ ├── Array+QuickSortLomutoScheme.swift │ ├── Array+RadixSort.swift │ └── Array+ShellSort.swift ├── ExtendedPhotoKit │ └── PHAsset │ │ └── PHAsset+URL.swift ├── ExtendedSceneKit │ ├── SCNAction+MoveAlong.swift │ └── SCNVector3+Operators.swift ├── ExtendedSpriteKit │ ├── SKEmitterNode │ │ └── SKEmitterNode+AdvanceSimulation.swift │ ├── SKScene │ │ ├── SKScene+ReferenceNodeFix.swift │ │ └── SKScene+SerialSpriteLoading.swift │ ├── SKSpriteNode │ │ └── SKSpriteNode+GIF.swift │ ├── SKTexture │ │ └── SKTexture+LinearGradient.swift │ ├── SKTextureAtlas │ │ └── SKTextureAtlas+FramesLoader.swift │ └── SKTimingFunction.swift ├── ExtendedUIKit │ ├── Badge │ │ └── Badge.swift │ ├── NSLayoutConstraint │ │ ├── NSLayoutConstraint+Activation.swift │ │ └── NSLayoutConstraint+Animation.swift │ ├── UIAlertController │ │ └── UIAlertController+Presentation.swift │ ├── UIApplication │ │ └── UIApplication+SafeAreas.swift │ ├── UIBezierPath │ │ ├── UIBezierPath+Convenience.swift │ │ └── UIBezierPath+RandomPoint.swift │ ├── UICollectionView │ │ ├── UICollectionView+CustomCellRegistration.swift │ │ ├── UICollectionView+Operations.swift │ │ ├── UICollectionView+Safety.swift │ │ └── UICollectionView+ScrollingUtils.swift │ ├── UIColor │ │ ├── UIColor+Blend.swift │ │ ├── UIColor+Brightness.swift │ │ └── UIColor+ColorComponents.swift │ ├── UIImage │ │ ├── UIImage+Downsample.swift │ │ ├── UIImage+ImageFromUIView.swift │ │ ├── UIImage+Inverted.swift │ │ ├── UIImage+LandscapeCameraOrientationFix.swift │ │ ├── UIImage+RawOrientation.swift │ │ ├── UIImage+Resize.swift │ │ └── UIImage+SolidColor.swift │ ├── UIImageView │ │ ├── UIImageView+DownloadFromURL.swift │ │ └── UIImageView+Masking.swift │ ├── UIResponder │ │ └── UIResponder+ChainDescription.swift │ ├── UIScreen │ │ └── UIScreen+InterfaceOrientation.swift │ ├── UITableView │ │ ├── UITableView+FooterHeaderUtils.swift │ │ ├── UITableView+Safety.swift │ │ └── UITableView+ScrollingUtils.swift │ ├── UIView │ │ ├── UIView+BezierRoundedCorners.swift │ │ ├── UIView+CACorners.swift │ │ ├── UIView+Constraints.swift │ │ ├── UIView+HuggingPriority.swift │ │ ├── UIView+LayoutAnimation.swift │ │ ├── UIView+Masking.swift │ │ └── UIView+Screenshot.swift │ ├── UIViewController │ │ ├── UIViewController+ChildViewControllers.swift │ │ ├── UIViewController+ContainerViewController.swift │ │ └── UIViewController+Storyboard.swift │ └── UIWindow │ │ └── UIWindow+Instantiate.swift └── Extendedos │ └── OSLog │ └── OSLog+LogLevels.swift ├── Tests ├── ExtendedFoundationSortingTests │ └── ArraySortingTests.swift ├── ExtendedFoundationTests │ ├── Array+ContainsTests.swift │ ├── Array+DifferenceTests.swift │ ├── Array+FilterTests.swift │ ├── Array+IntersectionTests.swift │ ├── Array+RemoveTests.swift │ ├── Array+UnionTests.swift │ ├── BoolTests.swift │ ├── DecodableTests.swift │ ├── DictionaryTests.swift │ ├── DoubleTests.swift │ ├── IntDecimalToBinaryTests.swift │ ├── IntTests.swift │ ├── NSObjectTests.swift │ ├── OptionSetTests.swift │ ├── SequenceTests.swift │ ├── StringTests.swift │ └── UnicodeOutputStreamTests.swift ├── LinuxMain.swift └── XCTestManifests.swift └── logo-extensions_kit.png /.gitattributes: -------------------------------------------------------------------------------- 1 | *.podspec linguist-detectable=false 2 | *.ruby linguist-detectable=false 3 | *.ru linguist-detectable=false 4 | *.spec linguist-detectable=false 5 | *.rbx linguist-detectable=false 6 | *.rabl linguist-detectable=false 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: macOS-latest 6 | steps: 7 | - uses: actions/checkout@v1 8 | 9 | - name: Switch to Xcode 11 10 | run: sudo xcode-select --switch /Applications/Xcode_11.3.app 11 | # Since we want to be running our tests from Xcode, we need to 12 | # generate an .xcodeproj file. Luckly, Swift Package Manager has 13 | # build in functionality to do so. 14 | - name: Generate xcodeproj 15 | run: swift package generate-xcodeproj 16 | - name: Run tests 17 | run: xcodebuild test -destination 'name=iPhone 11' -scheme 'ExtensionsKit-Package' 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## OS X Finder 2 | .DS_Store 3 | 4 | ## Build generated 5 | build/ 6 | DerivedData 7 | 8 | ## Various settings 9 | *.pbxuser 10 | !default.pbxuser 11 | *.mode1v3 12 | !default.mode1v3 13 | *.mode2v3 14 | !default.mode2v3 15 | *.perspectivev3 16 | !default.perspectivev3 17 | xcuserdata 18 | 19 | ## Other 20 | *.xccheckout 21 | *.moved-aside 22 | *.xcuserstate 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | 29 | # Swift Package Manager 30 | .build/ 31 | 32 | # Carthage 33 | Carthage/Build 34 | 35 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Astemir Eleev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "ExtensionsKit", 8 | platforms: [ 9 | .iOS(.v12) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 13 | .library( 14 | name: "ExtensionsKit", 15 | targets: [ 16 | "ExtendedAVFoundation", 17 | "ExtendedCoreAnimation", 18 | "ExtendedCoreGraphics", 19 | "ExtendedCoreImage", 20 | "ExtendedFoundation", 21 | "ExtendedFoundationDataStructures", 22 | "ExtendedFoundationSorting", 23 | "Extendedos", 24 | "ExtendedPhotoKit", 25 | "ExtendedSceneKit", 26 | "ExtendedSpriteKit", 27 | "ExtendedUIKit" 28 | ]), 29 | ], 30 | dependencies: [ 31 | // Dependencies declare other packages that this package depends on. 32 | // .package(url: /* package url */, from: "1.0.0"), 33 | ], 34 | targets: [ 35 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 36 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 37 | .target( 38 | name: "ExtendedAVFoundation", 39 | dependencies: []), 40 | .target( 41 | name: "ExtendedCoreAnimation", 42 | dependencies: []), 43 | .target( 44 | name: "ExtendedCoreGraphics", 45 | dependencies: []), 46 | .target( 47 | name: "ExtendedCoreImage", 48 | dependencies: []), 49 | .target( 50 | name: "ExtendedFoundation", 51 | dependencies: [ 52 | "ExtendedCoreImage" 53 | ]), 54 | .testTarget( 55 | name: "ExtendedFoundationTests", 56 | dependencies: [ 57 | "ExtendedFoundation" 58 | ]), 59 | .target( 60 | name: "ExtendedFoundationDataStructures", 61 | dependencies: []), 62 | .target( 63 | name: "ExtendedFoundationSorting", 64 | dependencies: []), 65 | .testTarget( 66 | name: "ExtendedFoundationSortingTests", 67 | dependencies: [ 68 | "ExtendedFoundationSorting" 69 | ]), 70 | .target( 71 | name: "Extendedos", 72 | dependencies: []), 73 | .target( 74 | name: "ExtendedPhotoKit", 75 | dependencies: []), 76 | .target( 77 | name: "ExtendedSceneKit", 78 | dependencies: [ 79 | "ExtendedCoreGraphics", 80 | "ExtendedUIKit" 81 | ]), 82 | .target( 83 | name: "ExtendedSpriteKit", 84 | dependencies: []), 85 | .target( 86 | name: "ExtendedUIKit", 87 | dependencies: [ 88 | "ExtendedFoundation" 89 | ]) 90 | ] 91 | ) 92 | -------------------------------------------------------------------------------- /Sources/ExtendedAVFoundation/AVCaptureDevice+ToggleFlash.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AVCaptureDevice+ToggleFlash.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 17/05/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | #if canImport(AVFoundation) && os(iOS) 10 | import AVFoundation 11 | 12 | // MARK: - AVCaptureDevice+Flashlight extnesion that adds support for flashlight capabilities and management through a simple method with custom states. 13 | extension AVCaptureDevice { 14 | 15 | // MARK: - Enums 16 | 17 | /// Is a convinience enum type for Flashlight feature. Incapsulates a number of possible states 18 | /// 19 | /// - on: for flashlight is being turned on 20 | /// - off: for flashlight is being turned off 21 | /// - unavailable: for cases when current device has no torch capabilities 22 | /// - undefined: for cases when none of the listed cases takes place 23 | /// - error: for cases when AVCaptureDevice has no suport for video type 24 | enum AVFlashlightState { 25 | case on 26 | case off 27 | case unavailable 28 | case undefined 29 | case error 30 | } 31 | 32 | /// Convenience enum that provies several levels of flashlight brigtness levels 33 | /// 34 | /// - none: 0.0 35 | /// - min: 0.25 36 | /// - mid: 0.5 37 | /// - high: 0.75 38 | /// - max: 1.0 39 | enum AVBrightnessLevel: Float { 40 | case none = 0.0 41 | case min = 0.25 42 | case mid = 0.5 43 | case high = 0.75 44 | case max = 1.0 45 | } 46 | 47 | // MARK: - Methods 48 | 49 | /// Toggls built in flashlight on and off 50 | /// 51 | /// - Returns: AVFlashlightState enum type describing one of the following states: 52 | @discardableResult static func toggleFlashlight(with brightnessLevel: AVBrightnessLevel = .max) -> AVFlashlightState { 53 | 54 | guard let device = AVCaptureDevice.default(for: .video) else { return AVFlashlightState.error } 55 | if device.hasTorch { 56 | do { 57 | try device.lockForConfiguration() 58 | 59 | if device.torchMode == .on { 60 | device.torchMode = .off 61 | return AVFlashlightState.off 62 | } else { 63 | do { 64 | try device.setTorchModeOn(level: brightnessLevel.rawValue) 65 | return AVFlashlightState.on 66 | } catch { 67 | debugPrint("Error when attemping to turn on torch -> ", error) 68 | } 69 | } 70 | } catch { 71 | debugPrint("Error when attempting to lock for torch configuration -> ", error) 72 | } 73 | } else { 74 | return AVFlashlightState.unavailable 75 | } 76 | return AVFlashlightState.undefined 77 | } 78 | } 79 | #endif 80 | -------------------------------------------------------------------------------- /Sources/ExtendedCoreAnimation/CAAnimation/CAAnimation+PatternReplicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CAAnimation+PatternReplicator.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 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | extension CAAnimation { 13 | 14 | @discardableResult 15 | public class func patternReplocator(with image: UIImage, 16 | size: CGSize, 17 | duration: CFTimeInterval = 2.0, 18 | opacity: ClosedRange = 0.1...1.0, 19 | delay: TimeInterval = 0.1, 20 | targetLayer: CALayer) -> CALayer { 21 | 22 | let replicatorLayer = CAReplicatorLayer() 23 | replicatorLayer.frame.size = size 24 | replicatorLayer.masksToBounds = true 25 | targetLayer.addSublayer(replicatorLayer) 26 | 27 | let imageLayer = CALayer() 28 | imageLayer.contents = image.cgImage 29 | imageLayer.frame.size = image.size 30 | replicatorLayer.addSublayer(imageLayer) 31 | 32 | let instanceCount = size.width / image.size.width 33 | replicatorLayer.instanceCount = Int(ceil(instanceCount)) 34 | 35 | replicatorLayer.instanceTransform = CATransform3DMakeTranslation( 36 | image.size.width, 0, 0 37 | ) 38 | let colorOffset = -1 / Float(replicatorLayer.instanceCount) 39 | replicatorLayer.instanceRedOffset = colorOffset 40 | replicatorLayer.instanceGreenOffset = colorOffset 41 | 42 | let verticalReplicatorLayer = CAReplicatorLayer() 43 | verticalReplicatorLayer.frame.size = size 44 | verticalReplicatorLayer.masksToBounds = true 45 | verticalReplicatorLayer.instanceBlueOffset = colorOffset 46 | targetLayer.addSublayer(verticalReplicatorLayer) 47 | 48 | let verticalInstanceCount = size.height / image.size.height 49 | verticalReplicatorLayer.instanceCount = Int(ceil(verticalInstanceCount)) 50 | verticalReplicatorLayer.instanceTransform = CATransform3DMakeTranslation( 51 | 0, image.size.height, 0 52 | ) 53 | verticalReplicatorLayer.addSublayer(replicatorLayer) 54 | 55 | let delay = TimeInterval(0.1) 56 | replicatorLayer.instanceDelay = delay 57 | verticalReplicatorLayer.instanceDelay = delay 58 | 59 | let animation = CABasicAnimation(keyPath: "transform.scale") 60 | animation.duration = 2 61 | animation.fromValue = 1 62 | animation.toValue = 0.1 63 | animation.autoreverses = true 64 | animation.repeatCount = .infinity 65 | animation.timingFunction = CAMediaTimingFunction.init(name: CAMediaTimingFunctionName.easeInEaseOut) 66 | imageLayer.add(animation, forKey: "wavyscale") 67 | 68 | return replicatorLayer 69 | } 70 | } 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /Sources/ExtendedCoreGraphics/CGFloat/CGFloat+Rounded.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGFloat+Rounded.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 16/08/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import CoreGraphics 10 | 11 | extension CGFloat { 12 | /// Rounds the double to decimal places value 13 | func rounded(toPlaces places: Int) -> CGFloat { 14 | let divisor = CGFloat(pow(10.0, CGFloat(places))) 15 | return (self * divisor).rounded() / divisor 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/ExtendedCoreGraphics/CGPoint/CGPoint+Operators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPoint+Operators.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 CoreGraphics 10 | 11 | extension CGPoint { 12 | 13 | public static func + (left: CGPoint, right: CGPoint) -> CGPoint { 14 | return CGPoint(x: left.x + right.x, y: left.y + right.y) 15 | } 16 | 17 | public static func += (left: inout CGPoint, right: CGPoint) { 18 | left = left + right 19 | } 20 | public static func + (left: CGPoint, right: CGVector) -> CGPoint { 21 | return CGPoint(x: left.x + right.dx, y: left.y + right.dy) 22 | } 23 | 24 | public static func += (left: inout CGPoint, right: CGVector) { 25 | left = left + right 26 | } 27 | public static func - (left: CGPoint, right: CGPoint) -> CGPoint { 28 | return CGPoint(x: left.x - right.x, y: left.y - right.y) 29 | } 30 | 31 | public static func -= (left: inout CGPoint, right: CGPoint) { 32 | left = left - right 33 | } 34 | 35 | public static func - (left: CGPoint, right: CGVector) -> CGPoint { 36 | return CGPoint(x: left.x - right.dx, y: left.y - right.dy) 37 | } 38 | 39 | public static func -= (left: inout CGPoint, right: CGVector) { 40 | left = left - right 41 | } 42 | 43 | public static func * (left: CGPoint, right: CGPoint) -> CGPoint { 44 | return CGPoint(x: left.x * right.x, y: left.y * right.y) 45 | } 46 | 47 | public static func *= (left: inout CGPoint, right: CGPoint) { 48 | left = left * right 49 | } 50 | 51 | public static func * (point: CGPoint, scalar: CGFloat) -> CGPoint { 52 | return CGPoint(x: point.x * scalar, y: point.y * scalar) 53 | } 54 | 55 | public static func *= (point: inout CGPoint, scalar: CGFloat) { 56 | point = point * scalar 57 | } 58 | 59 | public static func * (left: CGPoint, right: CGVector) -> CGPoint { 60 | return CGPoint(x: left.x * right.dx, y: left.y * right.dy) 61 | } 62 | 63 | public static func *= (left: inout CGPoint, right: CGVector) { 64 | left = left * right 65 | } 66 | 67 | public static func / (left: CGPoint, right: CGPoint) -> CGPoint { 68 | return CGPoint(x: left.x / right.x, y: left.y / right.y) 69 | } 70 | 71 | public static func /= (left: inout CGPoint, right: CGPoint) { 72 | left = left / right 73 | } 74 | 75 | public static func / (point: CGPoint, scalar: CGFloat) -> CGPoint { 76 | return CGPoint(x: point.x / scalar, y: point.y / scalar) 77 | } 78 | 79 | public static func /= (point: inout CGPoint, scalar: CGFloat) { 80 | point = point / scalar 81 | } 82 | 83 | public static func / (left: CGPoint, right: CGVector) -> CGPoint { 84 | return CGPoint(x: left.x / right.dx, y: left.y / right.dy) 85 | } 86 | 87 | public static func /= (left: inout CGPoint, right: CGVector) { 88 | left = left / right 89 | } 90 | 91 | public static func lerp(start: CGPoint, end: CGPoint, t: CGFloat) -> CGPoint { 92 | return start + (end - start) * t 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /Sources/ExtendedCoreGraphics/CGPoint/CGPoint+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPoint+Utils.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 CoreGraphics 10 | 11 | extension CGPoint { 12 | 13 | public func length() -> CGFloat { 14 | return sqrt(x*x + y*y) 15 | } 16 | 17 | public func lengthSquared() -> CGFloat { 18 | return x*x + y*y 19 | } 20 | 21 | func normalized() -> CGPoint { 22 | let len = length() 23 | return len > 0 ? self / len : CGPoint.zero 24 | } 25 | 26 | public mutating func normalize() -> CGPoint { 27 | self = normalized() 28 | return self 29 | } 30 | 31 | /// Computes the distance between the current [`self`] and the `other` point 32 | public func distance(to other: CGPoint) -> CGFloat { 33 | return sqrt(pow(x - other.x, 2) + pow(y - other.y, 2)) 34 | } 35 | 36 | /// Computes an angle in radians between the current [`self`] and the argument parameter `point` 37 | public func angle(to point: CGPoint) -> CGFloat { 38 | return atan2(point.y - y, point.x - x) 39 | } 40 | 41 | /// Returns the angle in radians of the vector described by the CGPoint. The range of the angle is -π to π; an angle of 0 points to the right. 42 | public var angle: CGFloat { 43 | return atan2(y, x) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/ExtendedCoreGraphics/CGRect/CGRect+AspectFit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGRect+AspectFit.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 15/05/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import CoreGraphics 10 | 11 | extension CGRect { 12 | 13 | func aspectFit(inRect target: CGRect) -> CGRect { 14 | let scale: CGFloat = { 15 | let scale = target.width / self.width 16 | 17 | return self.height * scale <= target.height ? 18 | scale : 19 | target.height / self.height 20 | }() 21 | 22 | let width = self.width * scale 23 | let height = self.height * scale 24 | let x = target.midX - width / 2 25 | let y = target.midY - height / 2 26 | 27 | return CGRect(x: x, y: y, width: width, height: height) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/ExtendedCoreGraphics/CGRect/CGRect+Corners.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGRect+Corners.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 CoreGraphics 10 | 11 | extension CGRect { 12 | 13 | public var topLeft: CGPoint{ 14 | return origin 15 | } 16 | 17 | public var topRight: CGPoint{ 18 | return CGPoint(x: origin.x + width, y: origin.y) 19 | } 20 | 21 | public var bottomLeft: CGPoint{ 22 | return CGPoint(x: origin.x, y: origin.y + height) 23 | } 24 | 25 | public var bottomRight: CGPoint{ 26 | return CGPoint(x: origin.x + width, y: origin.y + height) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/ExtendedCoreGraphics/CGRect/CGRect+Mid.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGRect+Mid.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 CoreGraphics 10 | 11 | extension CGRect { 12 | 13 | public var mid: CGPoint { 14 | return CGPoint(x: midX, y: midY) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/ExtendedCoreGraphics/CGRect/CGRect+Scale.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGRect+Scale.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 CoreGraphics 10 | 11 | extension CGRect { 12 | 13 | public func scaled(to size: CGSize) -> CGRect { 14 | return CGRect( 15 | x: self.origin.x * size.width, 16 | y: self.origin.y * size.height, 17 | width: self.size.width * size.width, 18 | height: self.size.height * size.height) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/ExtendedCoreGraphics/CGSize/CGSize+Operators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGSize+Operators.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 05/10/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import CoreGraphics 10 | 11 | extension CGSize { 12 | 13 | public static func +(left: CGSize, right: CGSize) -> CGSize { 14 | return CGSize(width: left.width + right.width, height: right.height + right.height) 15 | } 16 | 17 | public static func +=(left: inout CGSize, right: CGSize){ 18 | left = left + right 19 | } 20 | 21 | public static func -(left: CGSize, right: CGSize) -> CGSize { 22 | return CGSize(width: left.width - right.width, height: right.height - right.height) 23 | } 24 | 25 | public static func -=(left: inout CGSize, right: CGSize){ 26 | left = left - right 27 | } 28 | 29 | public static func *(left: CGSize, right: CGSize) -> CGSize { 30 | return CGSize(width: left.width * right.width, height: right.height * right.height) 31 | } 32 | 33 | public static func *=(left: inout CGSize, right: CGSize) { 34 | left = left * right 35 | } 36 | 37 | public static func /(left: CGSize, right: CGSize) -> CGSize { 38 | return CGSize(width: left.width / right.width, height: right.height / right.height) 39 | } 40 | 41 | public static func /=(left: inout CGSize, right: CGSize) { 42 | left = left / right 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/ExtendedCoreImage/CIImage+Inverted.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CIImage+Inverted.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 CoreImage.CIImage 10 | 11 | extension CIImage { 12 | 13 | /// Inverts the colors of `self` 14 | public var inverted: CIImage? { 15 | guard let invertedColorFilter = CIFilter(name: "CIColorInvert") else { return nil } 16 | 17 | invertedColorFilter.setValue(self, forKey: "inputImage") 18 | return invertedColorFilter.outputImage 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/ExtendedCoreImage/CIImage+QRImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CIImage+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 CoreImage.CIImage 10 | 11 | extension CIImage { 12 | 13 | /// Generates a QR image from the input `text` and with an optional `scale` parameter that specifies the affine transformation for the output `CIImage` 14 | public static func qrImage(from text: String, scaledBy scale: CGPoint = CGPoint(x: 10, y: 10)) -> CIImage? { 15 | guard let qrFilter = CIFilter(name: "CIQRCodeGenerator") else { return nil } 16 | let qrData = text.data(using: String.Encoding.ascii) 17 | qrFilter.setValue(qrData, forKey: "inputMessage") 18 | 19 | let qrTransform = CGAffineTransform(scaleX: scale.x, y: scale.y) 20 | return qrFilter.outputImage?.transformed(by: qrTransform) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/ExtendedCoreImage/CIImage+Tinted.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CIImage+Tinted.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 CoreImage.CIImage 10 | import UIKit.UIColor 11 | 12 | public extension CIImage { 13 | 14 | /// Applies the `color` as a tint color 15 | /// 16 | /// - Parameter color: is a UIColor instance that is used as a tint color 17 | /// - Returns: new optional CIImage that was tinted or nil if `CIMultiplyCompositing` or/and `CIConstantColorGenerator` filter(s) could not be created 18 | func tinted(by color: UIColor) -> CIImage? { 19 | guard 20 | let transparentQRImage = transparent, 21 | let filter = CIFilter(name: "CIMultiplyCompositing"), 22 | let colorFilter = CIFilter(name: "CIConstantColorGenerator") else { return nil } 23 | 24 | let ciColor = CIColor(color: color) 25 | colorFilter.setValue(ciColor, forKey: kCIInputColorKey) 26 | let colorImage = colorFilter.outputImage 27 | 28 | filter.setValue(colorImage, forKey: kCIInputImageKey) 29 | filter.setValue(transparentQRImage, forKey: kCIInputBackgroundImageKey) 30 | 31 | return filter.outputImage! 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/ExtendedCoreImage/CIImage+Transparent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CIImage+Transparent.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 CoreImage.CIImage 10 | 11 | extension CIImage { 12 | 13 | /// Creates a transparent image by inverting the colors of `self`. Target image should be black and white 14 | public var transparent: CIImage? { 15 | return inverted?.blackTransparent 16 | } 17 | 18 | /// Creates a a transparent image by converting all the black to transparent 19 | public var blackTransparent: CIImage? { 20 | guard let blackTransparentFilter = CIFilter(name: "CIMaskToAlpha") else { return nil } 21 | blackTransparentFilter.setValue(self, forKey: "inputImage") 22 | return blackTransparentFilter.outputImage 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/ExtendedCoreImage/Filters/HighlightFilter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HighlightFilter.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 07/09/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import CoreImage 10 | 11 | /// The filter is originally designed for highlighting 3D objects but can be used to add this effect to images and sprites 12 | public class HighlightFilter: CIFilter { 13 | 14 | // MARK: - Properties 15 | 16 | public static let filterName = "highlightFilter" 17 | 18 | @objc dynamic public var inputImage: CIImage? 19 | @objc dynamic public var inputIntensity: NSNumber? 20 | @objc dynamic public var inputRadius: NSNumber? 21 | 22 | // MARK: - Overrides 23 | 24 | public override var outputImage: CIImage? { 25 | guard let inputImage = inputImage else { 26 | return nil 27 | } 28 | let bloomFilter = prepareBloomFilter(for: inputImage) 29 | 30 | let sourceOverCompositing = CIFilter(name:"CISourceOverCompositing")! 31 | sourceOverCompositing.setValue(inputImage, forKey: "inputImage") 32 | sourceOverCompositing.setValue(bloomFilter.outputImage, forKey: "inputBackgroundImage") 33 | 34 | return sourceOverCompositing.outputImage 35 | } 36 | 37 | // MARK: - Private helpers 38 | 39 | private func prepareBloomFilter(for inputImage: CIImage) -> CIFilter { 40 | let bloomFilter = CIFilter(name:"CIBloom")! 41 | bloomFilter.setValue(inputImage, forKey: kCIInputImageKey) 42 | bloomFilter.setValue(inputIntensity, forKey: "inputIntensity") 43 | bloomFilter.setValue(inputRadius, forKey: "inputRadius") 44 | return bloomFilter 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Array/Array+Contains.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Contains.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 07/09/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array where Element: Equatable { 12 | 13 | /// Checks if self contains the specified elements 14 | /// 15 | /// - Parameter elements: is a vararg for elements that conform to Equatable protocol 16 | /// - Returns: true in cases when the array contains, otherwise false 17 | public func contains(elements: Element...) -> Bool { 18 | 19 | return elements.all { 20 | let result = self.firstIndex(of: $0) 21 | // Note that the force unwrapping here is safe because we explicitly checked whether the result is nil or not 22 | return result == nil ? false : result! >= 0 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Array/Array+Difference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Difference.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 07/09/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array where Element: Equatable { 12 | 13 | /// Computes differences between self and the input arrays 14 | /// 15 | /// - Parameter elements: is a vararg of arrays 16 | /// - Returns: array that contains difference 17 | public func difference(elements: [Element]...) -> [Element] { 18 | var result = [Element]() 19 | 20 | link: for item in self { 21 | for element in elements where element.contains(item) { 22 | continue link 23 | } 24 | result.append(item) 25 | } 26 | 27 | return result 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Array/Array+Filtering.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Filtering.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 Foundation 10 | 11 | extension Array { 12 | 13 | /// Skipps the n number of elements and returns the remainig of the array 14 | /// 15 | /// - Parameter n: number of elements to skip 16 | /// - Returns: new array with changed number of elements 17 | public func skip(_ n: Int) -> Array { 18 | let result: [Element] = [] 19 | return n > count ? result : Array(self[Int(n).. Bool) -> Bool { 27 | return filter(condition).count == count 28 | } 29 | 30 | /// Checks if any element in the given array satisfies a given condition 31 | /// 32 | /// - Parameter condition: a closure with one input element and bool as a return value 33 | /// - Returns: a bool value that indicates whether the condition was satisfied at least once 34 | public func any(condition: (Element) -> Bool) -> Bool { 35 | return filter(condition).count > 0 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Array/Array+Intersection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Intersection.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 07/09/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array where Element: Equatable { 12 | 13 | /// Computes intersection of self and the input values 14 | /// 15 | /// - Parameter values: vararg of arrays to intersect 16 | /// - Returns: an array that contains unique elements in all the input elements and self 17 | public func intersection(values: [Element]...) -> Array { 18 | var result = self 19 | var intersection = Array() 20 | 21 | for (index, value) in values.enumerated() { 22 | // By intersecting n and n + 1 we compute the intersection 23 | if (index > 0) { 24 | result = intersection 25 | intersection = Array() 26 | } 27 | 28 | // Searches for the common elements and saves them in the intersection Array, in order to intersect in the next iteration 29 | for elementValue in value where result.contains(elementValue) { 30 | intersection.append(elementValue) 31 | } 32 | } 33 | 34 | return intersection 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Array/Array+Remove.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Remove.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 | /// The extension adds support for convenience 'remove' methods for single elements, varargs and arrays of elements 12 | extension Array where Element: Equatable { 13 | 14 | /// Removes an Element by mutating the array 15 | @discardableResult 16 | public mutating func remove(object: Element) -> Element? { 17 | return firstIndex(of: object).map { self.remove(at: $0) } 18 | } 19 | 20 | /// Removes a number of Elements by mutating the origianl array 21 | public mutating func remove(objects: Element...) { 22 | remove(objects: objects) 23 | } 24 | 25 | /// Removes an array of Elements by mutating the origial array 26 | public mutating func remove(objects: [Element]) { 27 | objects.forEach { 28 | self.remove(object: $0) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Array/Array+Union.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Union.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 07/09/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array where Element: Equatable { 12 | 13 | /// Unions self and the input arrays 14 | /// 15 | /// - Parameter values: is a vararg for Arrays 16 | /// - Returns: a new Array containing merged elements 17 | public func union(values: [Element]...) -> Array { 18 | var result = self 19 | 20 | for array in values { 21 | for value in array where !result.contains(value) { 22 | result.append(value) 23 | } 24 | } 25 | 26 | return result 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Bool/Bool+Int.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bool+Int.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 Foundation 10 | 11 | extension Bool { 12 | 13 | public var int: Int { 14 | return self ? 1 : 0 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Bool/Bool+Random.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bool+Random.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 Foundation 10 | 11 | extension Bool { 12 | 13 | public static var random: Bool { 14 | return arc4random_uniform(2) == 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/ClosedRange/ClosedRange+Random.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Range+Random.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 29/04/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension ClosedRange { 12 | 13 | // MARK: - Properties 14 | 15 | public var randomInt: Int { 16 | get { 17 | var offset = 0 18 | 19 | if (lowerBound as! Int) < 0 { 20 | offset = abs(lowerBound as! Int) 21 | } 22 | 23 | let mini = UInt32(lowerBound as! Int + offset) 24 | let maxi = UInt32(upperBound as! Int + offset) 25 | 26 | return Int(mini + arc4random_uniform(maxi - mini)) - offset 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Collection/Collection+ParallelIteration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Collection+ParallelIteration.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 Foundation 10 | 11 | extension Collection { 12 | 13 | public func parallelForEach(_ each: (Self.Iterator.Element) -> Void) { 14 | let indicesArray = Array(indices) 15 | 16 | DispatchQueue.concurrentPerform(iterations: indicesArray.count) { (index) in 17 | let elementIndex = indicesArray[index] 18 | each(self[elementIndex]) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Collection/Collection+RandomItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Collection+RandomItem.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 Foundation 10 | 11 | extension Collection where Index == Int { 12 | 13 | public var randomItem: Element? { 14 | guard !isEmpty else { return nil } 15 | 16 | let index = Int(arc4random_uniform(UInt32(count))) 17 | return self[index] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Collection/Collection+SafeSubscript.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Collection+SafeSubscript.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 18/11/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Collection { 12 | 13 | /// Safely checks whether the collection is able to retreive an element for the given Index, otherwise it will return nil 14 | public subscript(safe index: Index) -> Element? { 15 | return indices.contains(index) ? self[index] : nil 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Collection/Collection+Sum&Average.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Collection+Sum&Average.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 Foundation 10 | 11 | extension Collection where Iterator.Element == IntegerLiteralType { 12 | 13 | public var sum: Element { 14 | return isEmpty ? 0 : reduce(0, +) 15 | } 16 | 17 | public var average: Element { 18 | return isEmpty ? 0 : reduce(0, +) / count 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Custom TextOutputStream/FileOutputStream.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileOutputStream.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 28/01/2019. 6 | // Copyright © 2019 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Prints the output stream to the specified file for the given `URL` and `encoding` 12 | public struct FileOutputStream: TextOutputStream { 13 | 14 | // MARK: - Properties 15 | 16 | private let fileHandle: FileHandle 17 | public let encoding: String.Encoding 18 | 19 | // MARK: - Initializers 20 | 21 | public init(_ fileHandle: FileHandle, encoding: String.Encoding = .utf8) { 22 | self.fileHandle = fileHandle 23 | self.encoding = encoding 24 | } 25 | 26 | // MARK: - Conformance to TextOutputStream proocol 27 | 28 | public mutating func write(_ string: String) { 29 | guard let data = string.data(using: encoding) else { return } 30 | fileHandle.write(data) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Custom TextOutputStream/UnicodeOutputStream.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnicodeOutputStream.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 28/01/2019. 6 | // Copyright © 2019 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Prints all the `Unicode` characters with the following scheme: 12 | /// 13 | /// Index: UnicodeScalar CodePoint (tabulation) UnicodeScalarName 14 | /// 15 | /// Can be attached the output completion handler in order to be able to debug the output stream 16 | public struct UnicodeOutputStream: TextOutputStream { 17 | 18 | // MARK: - Properties 19 | 20 | private var output: String = "" { 21 | didSet { 22 | outputCompletion?(output) 23 | } 24 | } 25 | internal var outputCompletion: ((String)->Void)? 26 | 27 | // MARK: - Conformance to TextOutputStream protocol 28 | 29 | public mutating func write(_ string: String) { 30 | guard !string.isEmpty, string != "\n" else { return } 31 | 32 | for (index, scalar) in string.unicodeScalars.lazy.enumerated() { 33 | let name = scalar.name ?? "" 34 | let codePoint = String(format: "U+%04X", scalar.value) 35 | 36 | output = "\(index): \(scalar) \(codePoint)\t\(name)" 37 | print(output) 38 | } 39 | } 40 | } 41 | 42 | // MARK: Internal extension for the Unicode.Scalar name 43 | 44 | extension Unicode.Scalar { 45 | var name: String? { 46 | guard var escapedName = 47 | "\(self)".applyingTransform(.toUnicodeName, 48 | reverse: false) 49 | else { 50 | return nil 51 | } 52 | 53 | escapedName.removeFirst(3) // remove "\\N{" 54 | escapedName.removeLast(1) // remove "}" 55 | 56 | return escapedName 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/CustomProtocols/Identifiable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Identifiable.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 | 11 | /// Identifiable protocol supposed to be used with types that need identification, such as UITableViewCell, UITableViewHeaderFooterView, UICollectionReusableView etc. 12 | /// The current implementation does not add the protocol extension for the aforementioned types, since it's up to you, the developer to decide which types should receive such functionality. 13 | public protocol Identifiable { 14 | static var reuseIdentifier: String { get } 15 | } 16 | 17 | extension Identifiable { 18 | public static var reuseIdentifier: String { 19 | return String(describing: self) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/CustomProtocols/Then.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Then.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 | 11 | /// Protocol for object configuration 12 | /// https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/util/Standard.kt 13 | /// 14 | /// For instnace you can extend a type and be able to repeadadly call `then` in order to configure it. It's a "sort of" Builder design pattern, but more modern reincarnation. 15 | /// 16 | /// Example: 17 | /// var imageView = UIImageView().then { 18 | /// $0.layer.cornerRadius = 10 19 | /// $0.contentMode = .scaleAspectFill 20 | /// $0.clipsToBounds = true 21 | /// } 22 | /// 23 | public protocol Then { /* empty implementation */ } 24 | 25 | extension Then { 26 | public func then(configure: (Self) -> Void) -> Self { 27 | configure(self) 28 | return self 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/CustomTypes/Debouncer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Debouncer.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 | 11 | /// Allows an action to be performed after a delay 12 | public final class Debouncer { 13 | 14 | // MARK: - Properties 15 | 16 | private let delay: TimeInterval 17 | private var workItem: DispatchWorkItem? 18 | 19 | // MARK: - Initializers 20 | 21 | init(delay: TimeInterval) { 22 | self.delay = delay 23 | } 24 | 25 | // MARK: - Methods 26 | 27 | /// Trigger the action after some delay 28 | 29 | /// Action triggerer after the specified delay 30 | func schedule(action: @escaping () -> Void) { 31 | workItem?.cancel() 32 | workItem = DispatchWorkItem(block: action) 33 | DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem!) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/CustomTypes/Variable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Variable.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 | /// Lightweight bindable data type that allows to get on update notifications for a given value. Can be used with MVVM or any another architectural pattern to replace the need for 3rd party, heavyweight binding framework. 12 | public class Variable { 13 | 14 | public var value: Value { 15 | didSet { 16 | onUpdate?(value) 17 | } 18 | } 19 | 20 | public var onUpdate: ((Value) -> Void)? { 21 | didSet { 22 | onUpdate?(value) 23 | } 24 | } 25 | 26 | public init(_ value: Value, _ onUpdate: ((Value) -> Void)? = nil) { 27 | self.value = value 28 | self.onUpdate = onUpdate 29 | self.onUpdate?(value) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Data/Data+Bytes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data+Bytes.swift 3 | // ExtendedFoundation 4 | // 5 | // Created by Astemir Eleev on 23/02/2020. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Data { 12 | var bytes: [UInt8] { 13 | return [UInt8](self) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Data/Data+Matches.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data+Matches.swift 3 | // ExtendedFoundation 4 | // 5 | // Created by Astemir Eleev on 23/02/2020. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Data { 12 | 13 | func matches(bytes: [UInt8], range: CountableClosedRange? = nil) -> Bool { 14 | return Array(self.bytes[range ?? 0...(bytes.count - 1)]) == bytes 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Data/Data+MimeType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data+MimeType.swift 3 | // ExtendedFoundation 4 | // 5 | // Created by Astemir Eleev on 23/02/2020. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Data { 12 | 13 | // MARK: - Image Types 14 | 15 | var isPng: Bool { 16 | return matches(bytes: [0x89, 0x50, 0x4E, 0x47]) 17 | } 18 | 19 | var isJPEG: Bool { 20 | return matches(bytes: [0xFF, 0xD8, 0xFF]) 21 | } 22 | 23 | var isJpg: Bool { 24 | return isJPEG 25 | } 26 | 27 | var isGif: Bool { 28 | return matches(bytes: [0x47, 0x49, 0x46]) 29 | } 30 | 31 | var isWebp: Bool { 32 | return matches(bytes: [0x57, 0x45, 0x42, 0x50], range: 8...11) 33 | } 34 | 35 | var isTiff: Bool { 36 | return matches(bytes: [0x49, 0x49, 0x2A, 0x00]) || 37 | matches(bytes: [0x4D, 0x4D, 0x00, 0x2A]) 38 | } 39 | 40 | var isBmp: Bool { 41 | return matches(bytes: [0x42, 0x4D]) 42 | } 43 | 44 | var isPsd: Bool { 45 | return matches(bytes: [0x38, 0x42, 0x50, 0x53]) 46 | } 47 | 48 | // MARK: - Audio Types 49 | 50 | var isAmr: Bool { 51 | return matches(bytes: [0x23, 0x21, 0x41, 52 | 0x4D, 0x52, 0x0A]) 53 | } 54 | 55 | var isMp3: Bool { 56 | return matches(bytes: [0x49, 0x44, 0x33]) || 57 | matches(bytes: [0xFF, 0xFB]) 58 | } 59 | 60 | var isOgg: Bool { 61 | return matches(bytes: [0x4F, 0x67, 0x67, 0x53]) 62 | } 63 | 64 | var isFlac: Bool { 65 | return matches(bytes: [0x66, 0x4C, 0x61, 0x43]) 66 | } 67 | 68 | var isWav: Bool { 69 | return matches(bytes: [0x52, 0x49, 0x46, 0x46]) && 70 | matches(bytes: [0x57, 0x41, 0x56, 0x45], range: 8...11) 71 | } 72 | 73 | var isMid: Bool { 74 | return matches(bytes: [0x4D, 0x54, 0x68, 0x64]) 75 | } 76 | 77 | var isM4a: Bool { 78 | return matches(bytes: [0x4D, 0x34, 0x41, 0x20]) || 79 | matches(bytes: [0x66, 0x74, 0x79, 0x70, 80 | 0x4D, 0x34, 0x41], 81 | range: 4...10) 82 | } 83 | 84 | var isOpus: Bool { 85 | return matches(bytes: [0x4F, 0x70, 0x75, 0x73, 86 | 0x48, 0x65, 0x61, 0x64], 87 | range: 28...35) 88 | } 89 | 90 | // MARK: - Application Types 91 | 92 | var isIco: Bool { 93 | return matches(bytes: [0x00, 0x00, 0x01, 0x00]) 94 | } 95 | 96 | var isSqlite: Bool { 97 | return matches(bytes: [0x53, 0x51, 0x4C, 0x69]) 98 | } 99 | 100 | var isTar: Bool { 101 | return matches(bytes: [0x75, 0x73, 0x74, 0x61, 0x72], 102 | range: 257...261) 103 | } 104 | 105 | var isRar: Bool { 106 | return matches(bytes: [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07]) && 107 | (matches(bytes: [0x0], range: 6...6) || 108 | matches(bytes: [0x1], range: 6...6)) 109 | } 110 | 111 | var isGzip: Bool { 112 | return matches(bytes: [0x1F, 0x8B, 0x08]) 113 | } 114 | 115 | var isBz2: Bool { 116 | return matches(bytes: [0x42, 0x5A, 0x68]) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Date/Date+FirstLast.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+FirstLast.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 | extension Date { 12 | 13 | public var firstDayOfWeek: Date { 14 | var beginningOfWeek = Date() 15 | var interval = TimeInterval() 16 | 17 | _ = Calendar.current.dateInterval(of: .weekOfYear, start: &beginningOfWeek, interval: &interval, for: self) 18 | return beginningOfWeek 19 | } 20 | 21 | public var startOfDay: Date { 22 | return Calendar.current.startOfDay(for: self) 23 | } 24 | 25 | public var endOfDay: Date { 26 | let cal = Calendar.current 27 | var components = DateComponents() 28 | components.day = 1 29 | return cal.date(byAdding: components, to: self.startOfDay)!.addingTimeInterval(-1) 30 | } 31 | 32 | public var numberOfDaysInMonth: Int { 33 | let calendar = Calendar.current 34 | let dayRange = (calendar as NSCalendar).range(of: NSCalendar.Unit.day, in: NSCalendar.Unit.month, for: self) 35 | 36 | return dayRange.length 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Date/Date+PreviousNext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+PreviousNext.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 | extension Date { 12 | 13 | public func nextDate() -> Date { 14 | let nextDate = Calendar.current.date(byAdding: .day, value: 1, to: self) 15 | return nextDate ?? Date() 16 | } 17 | 18 | public func previousDate() -> Date { 19 | let previousDate = Calendar.current.date(byAdding: .day, value: -1, to: self) 20 | return previousDate ?? Date() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Decodable/Decodable+DecodeFromFile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Decodable+DecodeFromFile.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 24/12/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Decodable { 12 | 13 | /// Decodes a file into a type 14 | /// 15 | /// - Parameters: 16 | /// - fileName: the target file name that will be used to decode into a type 17 | /// - fileExtension: a file's extension. The default value is ".json" 18 | /// - bundle: the app's bundle, The default value is ".main" 19 | /// - Returns: an instantiated object of type Self 20 | /// - Throws: may throw an error of type DecodableError, more specifically .missingFile 21 | public static func decodeFromFile( 22 | named fileName: String, 23 | with fileExtension: String = "json", 24 | in bundle: Bundle = .main 25 | ) throws -> Self { 26 | guard let url = bundle.url(forResource: fileName, 27 | withExtension: fileExtension) else { 28 | throw DecodableError.missingFile 29 | } 30 | 31 | let data = try Data(contentsOf: url) 32 | let decoder = JSONDecoder() 33 | 34 | return try decoder.decode(self, from: data) 35 | } 36 | } 37 | 38 | 39 | public enum DecodableError: Error { 40 | case missingFile 41 | } 42 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Dictionary/Dictionary+ConvenienceWrappers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+ConvenienceWrappers.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 07/09/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Dictionary { 12 | 13 | public func has(key: Key) -> Bool { 14 | return self[key] != nil 15 | } 16 | 17 | public func each (each: (Key, Value) -> ()) { 18 | for (key, value) in self { 19 | each(key, value) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Dictionary/Dictionary+Difference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+Difference.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 07/09/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Dictionary where Value: Equatable { 12 | 13 | /// Computes differences between self and the input dictionaries 14 | /// 15 | /// - Parameter dictionaries: is a vararg of dictionaries 16 | /// - Returns: dictionary that contains differences 17 | func difference(dictionaries: [Key: Value]...) -> [Key: Value] { 18 | var result = [Key: Value]() 19 | 20 | each { result[$0] = $1 } 21 | 22 | for dictionary in dictionaries { 23 | for (key, value) in dictionary where result.has(key: key) && result[key] == value { 24 | result.removeValue(forKey: key) 25 | } 26 | } 27 | 28 | return result 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Dictionary/Dictionary+GetOrAddValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+GetOrAddValue.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 06/06/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Dictionary { 12 | 13 | /// Checks for a value for a given key or creates a new key/value pair if none was found 14 | public mutating func value(for key: Key, orAdd valueClosure: @autoclosure () -> Value) -> Value { 15 | if let value = self[key] { 16 | return value 17 | } 18 | 19 | let value = valueClosure() 20 | self[key] = value 21 | return value 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Dictionary/Dictionary+Intersection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+Intersection.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 07/09/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Dictionary where Key: Equatable, Value: Equatable { 12 | 13 | /// Computes intersection of self and the input Dictionaries 14 | /// 15 | /// - Parameter dictionaries: vararg of dictionaries to intersect 16 | /// - Returns: a dictionary that contains unique keys and values in all the input dictionaries and self 17 | public func intersection(dictionaries: [Key: Value]...) -> [Key: Value] { 18 | let filtered = map { (item, value) -> (Key, Value)? in 19 | return (item, value) 20 | } 21 | 22 | return filtered.filter { (key: Key, value: Value) -> Bool in 23 | dictionaries.all { $0.has(key: key) && $0[key] == value } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Dictionary/Dictionary+JSON.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+JSON.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 Foundation 10 | 11 | extension Dictionary { 12 | 13 | public func jsonData(prettify: Bool = false) -> Data? { 14 | guard JSONSerialization.isValidJSONObject(self) else { 15 | return nil 16 | } 17 | let options = (prettify == true) ? JSONSerialization.WritingOptions.prettyPrinted : JSONSerialization.WritingOptions() 18 | return try? JSONSerialization.data(withJSONObject: self, options: options) 19 | } 20 | 21 | public func jsonString(prettify: Bool = false) -> String? { 22 | guard JSONSerialization.isValidJSONObject(self) else { return nil } 23 | let options = (prettify == true) ? JSONSerialization.WritingOptions.prettyPrinted : JSONSerialization.WritingOptions() 24 | guard let jsonData = try? JSONSerialization.data(withJSONObject: self, options: options) else { return nil } 25 | return String(data: jsonData, encoding: .utf8) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Dictionary/Dictionary+Map.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+Map.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 07/09/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Dictionary { 12 | 13 | /// Filters out self with the same keys as the 'map' input parameter, and values generated by running each [key: value] by dicscarding nil values and mapping keys with values 14 | public func map(_ map: (Key, Value) -> (Key, Value)?) -> [Key: Value] { 15 | var mapped = [Key: Value]() 16 | 17 | each { 18 | if let value = map($0, $1) { 19 | mapped[value.0] = value.1 20 | } 21 | } 22 | 23 | return mapped 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Dictionary/Dictionary+Union.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+Union.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 07/09/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Dictionary { 12 | 13 | /// Unions self and the input dictionaries 14 | /// 15 | /// - Parameter dictionaries: is a vararg for Dictionaries 16 | /// - Returns: a new Dictionary containing merged keys and values 17 | public func union(dictionaries: Dictionary...) -> Dictionary { 18 | var result = self 19 | 20 | dictionaries.forEach { (dictionary) in 21 | dictionary.forEach { (key, value) in 22 | _ = result.updateValue(value, forKey: key) 23 | } 24 | } 25 | 26 | return result 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Double/Double+CurrencyShortcuts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Double+CurrencyShortcuts.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 Double { 12 | 13 | public var usd: String? { 14 | return formatter(for: "en_US_POSIX") 15 | } 16 | 17 | public var ruble: String? { 18 | return formatter(for: "ru_RU") 19 | } 20 | 21 | // MARK: - Private methods 22 | 23 | private func formatter(for identifier: String) -> String? { 24 | let formatter = NumberFormatter() 25 | formatter.numberStyle = .currency 26 | formatter.locale = Locale(identifier: identifier) 27 | 28 | let number = NSNumber(value: self) 29 | return formatter.string(from: number) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Double/Double+Rounded.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Double+Rounded.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 16/08/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Double { 12 | /// Rounds the double to decimal places value 13 | public func rounded(toPlaces places: Int) -> Double { 14 | let divisor = pow(10.0, Double(places)) 15 | return (self * divisor).rounded() / divisor 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Float/Float+Rounded.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Float+Rounded.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 16/08/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import CoreGraphics 10 | 11 | extension Float { 12 | /// Rounds the double to decimal places value 13 | func rounded(toPlaces places:Int) -> Float { 14 | let divisor = Float(pow(10.0, Float(places))) 15 | return (self * divisor).rounded() / divisor 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Functions/FunctionalComposition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionalComposition.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 | precedencegroup ComposePrecedence { 12 | associativity: left 13 | } 14 | 15 | infix operator ->> : ComposePrecedence 16 | infix operator <<- : ComposePrecedence 17 | 18 | /// Composes two functions into one by using functional composition (left-to-right order e.g. (x) = rhs(lhs(x))) 19 | public func ->> (lhs: @escaping (T) -> U, rhs: @escaping (U) -> V) -> ((T) -> V) { 20 | return { rhs(lhs($0)) } 21 | } 22 | 23 | /// Composes two function into one by using functional compoisition (right-to-left order e.g. (x) = lhs(rhs(x))) 24 | public func <<- (lhs: @escaping (U) -> V, rhs: @escaping (T) -> U) -> ((T) -> V) { 25 | return { lhs(rhs($0)) } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Int/Int+Clamp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int+Clamp.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 Foundation 10 | 11 | extension Int { 12 | 13 | public func clamp(min: Int, _ max: Int) -> Int { 14 | return Swift.max(min, Swift.min(max, self)) 15 | } 16 | 17 | public func clamp(in range: ClosedRange) -> Int { 18 | return clamp(min: range.lowerBound, range.upperBound) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Int/Int+DecimalToBinary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int+DecimalToBinary.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 Foundation 10 | 11 | extension Int { 12 | 13 | /// Converts a decimal number to a binary number using the conventional algorithm. 14 | /// 15 | /// - Returns: a binary representation of 'self' 16 | public func decimalToBinary() -> Int { 17 | var decimal = 0, binary = 0, c = -1 18 | var selfCopy = self 19 | 20 | while selfCopy != 0 { 21 | decimal = selfCopy % 2 22 | c += 1 23 | binary += decimal * Int(truncating: pow(10, c) as NSDecimalNumber) 24 | selfCopy /= 2 25 | } 26 | return binary 27 | } 28 | 29 | /// Converts a binary number to a decimal number using the conventional algorithm. 30 | /// 31 | /// - Returns: a decimal representation of `self` 32 | public func binaryToDecimal() -> Int { 33 | var k = self, d = 0, s = 0, c = -1 34 | 35 | while k != 0 { 36 | d = k % 10 37 | c += 1 38 | s += d * Int(truncating: pow(2, c) as NSDecimalNumber) 39 | k /= 10 40 | } 41 | return s 42 | } 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Int/Int+Digits.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int+Digits.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 29/04/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Int { 12 | 13 | // MARK: - Properties 14 | 15 | public var digitCount: Int { 16 | return counter(for: self) 17 | } 18 | 19 | // MARK: - Methods 20 | 21 | private func counter(for number: Int) -> Int { 22 | if abs(number) < 10 { 23 | return 1 24 | } else { 25 | return 1 + counter(for: number / 10) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Int/Int+EvenOdd.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int+EvenOdd.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 Foundation 10 | 11 | extension Int { 12 | 13 | /// Checks if a number is even 14 | /// 15 | /// - Returns: true if self is even 16 | public func isEven () -> Bool { 17 | return (self % 2) == 0 18 | } 19 | 20 | /// Checks if a number is odd 21 | /// 22 | /// - Returns: true if self is odd 23 | public func isOdd () -> Bool { 24 | return !isEven() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Int/Int+Factorial.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int+Factorial.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 Foundation 10 | 11 | extension Int { 12 | 13 | /// Computes factorial of self 14 | /// 15 | /// - Returns: factorial 16 | public func factorial () -> Int { 17 | return self == 0 ? 1 : self * (self - 1).factorial() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Int/Int+Power.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int+Power.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 29/04/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | precedencegroup PowerPrecedence { higherThan: MultiplicationPrecedence } 12 | infix operator ^^ : PowerPrecedence 13 | 14 | func ^^(radix: Int, power: Int) -> Int { 15 | return Int(pow(Double(radix), Double(power))) 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Int/Int+Random.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int+Random.swift 3 | // extensions-kit 4 | // 5 | // Created by Astemir Eleev on 29/04/2018. 6 | // Copyright © 2018 Astemir Eleev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Int { 12 | 13 | // MARK: - Methods 14 | 15 | static func random(in range: ClosedRange) -> Int { 16 | var offset = 0 17 | 18 | if range.lowerBound < 0 { 19 | offset = abs(range.lowerBound) 20 | } 21 | 22 | let mini = UInt32(range.lowerBound + offset) 23 | let maxi = UInt32(range.upperBound + offset) 24 | 25 | return Int(mini + arc4random_uniform(maxi - mini)) - offset 26 | } 27 | 28 | static func random(min: Int = 0, max: Int) -> Int { 29 | return Int(arc4random_uniform(UInt32(max - min))) + min 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Sources/ExtendedFoundation/Int/Int+Roman.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int+Roman.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 Foundation 10 | 11 | extension Int { 12 | 13 | public func romanNumeral() -> String? { 14 | guard self > 0 else { 15 | return nil 16 | } 17 | let romanValues = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"] 18 | let arabicValues = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1] 19 | 20 | var romanValue = "" 21 | var startingValue = self 22 | 23 | for (index, romanChar) in romanValues.enumerated() { 24 | let arabicValue = arabicValues[index] 25 | let div = startingValue / arabicValue 26 | if div > 0 { 27 | for _ in 0..
(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 --------------------------------------------------------------------------------