├── .build └── manifest.db ├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Package.swift ├── README.md ├── Sources └── Helper4Swift │ ├── Extensions │ ├── Array+Extension.swift │ ├── Binding+Extension.swift │ ├── Bundle+Extension.swift │ ├── CALayer+Extension.swift │ ├── CLLocationCoordinate2D+Extension.swift │ ├── CheckedContinuation+Extension.swift │ ├── Color+Extension.swift │ ├── Date+Extension.swift │ ├── Dictionary+Extension.swift │ ├── Dispatch+Extension.swift │ ├── Encodable+Extension.swift │ ├── Int+Extension.swift │ ├── JSONDecoder+Extension.swift │ ├── MKCoordinateSpan+Extension.swift │ ├── NSMutableAttributedString+Extension.swift │ ├── Optional+Extension.swift │ ├── Sequence+Extension.swift │ ├── String+Extension.swift │ ├── TimeInterval+Extension.swift │ ├── UIApplication+Extension.swift │ ├── UIButton+Extension.swift │ ├── UICollectionView+Extension.swift │ ├── UIColor+Extension.swift │ ├── UIImage+Extension.swift │ ├── UIImageView+Extension.swift │ ├── UIKeyboardType+Extension.swift │ ├── UILabel+Extension.swift │ ├── UINavigationController+Extension.swift │ ├── UIStoryboard+Extension.swift │ ├── UITabBarController+Extension.swift │ ├── UITableView+Extension.swift │ ├── UITextField+Extension.swift │ ├── UITextView+Extension.swift │ ├── UIView+Extension.swift │ ├── UIViewAutoLayout+Extension.swift │ ├── UIViewController+Extension.swift │ ├── UIViewController+LocalAuthentication+Extension.swift │ └── URL+Extension.swift │ ├── Helper4Swift.swift │ └── SubClasses │ └── UIViewFromNib.swift └── Tests ├── Helper4SwiftTests ├── Helper4SwiftTests.swift └── XCTestManifests.swift └── LinuxMain.swift /.build/manifest.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cs4alhaider/Helper4Swift/0b8c305dddbc7486c5e056416008c1aa18e6cf73/.build/manifest.db -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 26 | # Carthage/Checkouts 27 | 28 | Carthage/Build 29 | 30 | # We recommend against adding the Pods directory to your .gitignore. However 31 | # you should judge for yourself, the pros and cons are mentioned at: 32 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 33 | # 34 | # Note: if you ignore the Pods directory, make sure to uncomment 35 | # `pod install` in .travis.yml 36 | # 37 | # Pods/ 38 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 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: "Helper4Swift", 8 | platforms: [ 9 | .iOS(SupportedPlatform.IOSVersion.v13), 10 | .tvOS(SupportedPlatform.TVOSVersion.v10), 11 | .watchOS(SupportedPlatform.WatchOSVersion.v3), 12 | .macOS(SupportedPlatform.MacOSVersion.v10_13) 13 | ], 14 | products: [ 15 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 16 | .library( 17 | name: "Helper4Swift", 18 | targets: ["Helper4Swift"]), 19 | ], 20 | dependencies: [ 21 | // Dependencies declare other packages that this package depends on. 22 | // .package(url: /* package url */, from: "1.0.0"), 23 | ], 24 | targets: [ 25 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 26 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 27 | .target( 28 | name: "Helper4Swift", 29 | dependencies: []), 30 | .testTarget( 31 | name: "Helper4SwiftTests", 32 | dependencies: ["Helper4Swift"]), 33 | ] 34 | ) 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Helper4Swift 2 | 3 | [![Twitter: @cs_alhaider](https://img.shields.io/badge/contact-%40cs_alhaider-blue.svg)](https://twitter.com/cs_alhaider) 4 | 5 | 6 | Helpful extensions and methods to short your coding time. 7 | 8 | 9 | ## Available Helpers 10 | 11 | - [x] Array 12 | - [x] Bundle 13 | - [x] CALayer 14 | - [x] Date 15 | - [x] Dictionary 16 | - [x] Dispatch 17 | - [x] Encodable 18 | - [x] Int 19 | - [x] JSONDecoder 20 | - [x] String 21 | - [x] NSMutableAttributedString 22 | - [x] UIApplication 23 | - [x] UIButton 24 | - [x] UICollection 25 | - [x] UIColor 26 | - [x] UIImage 27 | - [x] UIImageView 28 | - [x] UIKeyboardType 29 | - [x] UILabel 30 | - [x] UINavigationController 31 | - [x] UIStoryboard 32 | - [x] UITabBarController 33 | - [x] UITableView 34 | - [x] UITextField 35 | - [x] UITextView 36 | - [x] UIView 37 | - [x] UIViewAutoLayout 38 | - [x] UIViewController 39 | 40 | 41 | ## Requirements 42 | 43 | Xcode 10* , Swift 5 44 | 45 | 46 | ## Author 47 | 48 | Abdullah Alhaider, 49 |
50 | cs.alhaider@gmail.com 51 |
52 | [Twitter](https://twitter.com/cs_alhaider) 53 | 54 | ## License 55 | 56 | Helper4Swift is available under the MIT license. See the LICENSE file for more info. 57 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/Array+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 6/18/18. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension Array { 11 | 12 | /// Random item from array. 13 | var randomItem: Element? { 14 | if self.isEmpty { return nil } 15 | let index = Int(arc4random_uniform(UInt32(count))) 16 | return self[index] 17 | } 18 | 19 | /// Shuffled version of array. 20 | var shuffled: [Element] { 21 | var arr = self 22 | for _ in 0..<10 { 23 | arr.sort { (_, _) in arc4random() < arc4random() } 24 | } 25 | return arr 26 | } 27 | 28 | /// Shuffle array. 29 | mutating func shuffle() { 30 | // https://gist.github.com/ijoshsmith/5e3c7d8c2099a3fe8dc3 31 | for _ in 0..<10 { 32 | sort { (_, _) in arc4random() < arc4random() } 33 | } 34 | } 35 | 36 | /// Element at the given index if it exists. 37 | /// 38 | /// - Parameter index: index of element. 39 | /// - Returns: optional element (if exists). 40 | func item(at index: Int) -> Element? { 41 | guard index >= 0 && index < count else { return nil } 42 | return self[index] 43 | } 44 | 45 | @discardableResult 46 | mutating func append(_ newArray: Array) -> CountableRange { 47 | let range = count..<(count + newArray.count) 48 | self += newArray 49 | return range 50 | } 51 | 52 | @discardableResult 53 | mutating func insert(_ newArray: Array, at index: Int) -> CountableRange { 54 | let mIndex = Swift.max(0, index) 55 | let start = Swift.min(count, mIndex) 56 | let end = start + newArray.count 57 | 58 | let left = self[0.. (_ element: T) { 65 | let anotherSelf = self 66 | removeAll(keepingCapacity: true) 67 | anotherSelf.each { (_: Int, current: Element) in 68 | if (current as? T) !== element { 69 | self.append(current) 70 | } 71 | } 72 | } 73 | 74 | func each(_ exe: (Int, Element) -> Void) { 75 | for (index, item) in enumerated() { 76 | exe(index, item) 77 | } 78 | } 79 | 80 | } 81 | 82 | public extension Array where Element: Equatable { 83 | 84 | /// Remove Dublicates. 85 | var unique: [Element] { 86 | return self.reduce([]) { $0.contains($1) ? $0 : $0 + [$1] } 87 | } 88 | 89 | /// Check if array contains an array of elements. 90 | /// 91 | /// - Parameter elements: array of elements to check. 92 | /// - Returns: true if array contains all given items. 93 | func contains(_ elements: [Element]) -> Bool { 94 | guard !elements.isEmpty else { 95 | return false 96 | } 97 | var found = true 98 | for element in elements { 99 | if !contains(element) { 100 | found = false 101 | } 102 | } 103 | return found 104 | } 105 | 106 | /// All indexes of specified item. 107 | /// 108 | /// - Parameter item: item to check. 109 | /// - Returns: an array with all indexes of the given item. 110 | func indexes(of item: Element) -> [Int] { 111 | var indexes: [Int] = [] 112 | for index in 0.. [[Element]] { 132 | var result = [[Element]]() 133 | var chunk = -1 134 | for (index, elem) in self.enumerated() { 135 | if index % size == 0 { 136 | result.append([Element]()) 137 | chunk += 1 138 | } 139 | result[chunk].append(elem) 140 | } 141 | return result 142 | } 143 | 144 | /// Description 145 | /// 146 | /// - Parameter map: uniqueSet 147 | /// - Returns: unique Array 148 | func uniqueSet (map: ((Element) -> (T))) -> [Element] { 149 | var set = Set() // the unique list kept in a Set for fast retrieval 150 | var arrayOrdered = [Element]() // keeping the unique list of elements but ordered 151 | for value in self { 152 | if !set.contains(map(value)) { 153 | set.insert(map(value)) 154 | arrayOrdered.append(value) 155 | } 156 | } 157 | return arrayOrdered 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/Binding+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Binding+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 04/01/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @available(iOS 13.0, *) 11 | public extension Binding { 12 | 13 | /// Get notified when the value changed 14 | /// 15 | /// - Parameter handler: your own logic 16 | /// 17 | /// - Returns: same value 18 | func onChange(_ handler: @escaping (Value) -> Void) -> Binding { 19 | Binding( 20 | get: { self.wrappedValue }, 21 | set: { newValue in 22 | self.wrappedValue = newValue 23 | handler(newValue) 24 | } 25 | ) 26 | } 27 | } 28 | 29 | @available(iOS 13.0, *) 30 | public func ??(lhs: Binding>, rhs: T) -> Binding { 31 | Binding( 32 | get: { lhs.wrappedValue ?? rhs }, 33 | set: { lhs.wrappedValue = $0 } 34 | ) 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/Bundle+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 18/05/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension Bundle { 11 | 12 | /// Decode an exesting json file 13 | /// 14 | /// ``` 15 | /// let model = Bundle.main.decode([Model].self, from: "modelss.json") 16 | /// ``` 17 | /// 18 | /// - Parameters: 19 | /// - type: Decodable object 20 | /// - file: file name or uel 21 | /// - Returns: decoded object 22 | func decode(_ type: T.Type, from file: String) -> T { 23 | guard let url = self.url(forResource: file, withExtension: nil) else { 24 | fatalError("Failed to locate \(file) in app bundle.") 25 | } 26 | guard let data = try? Data(contentsOf: url) else { 27 | fatalError("Failed to load \(file) in app bundle.") 28 | } 29 | 30 | let decoder = JSONDecoder() 31 | 32 | guard let loaded = try? decoder.decode(T.self, from: data) else { 33 | fatalError("Failed to decode \(file) from app bundle.") 34 | } 35 | return loaded 36 | } 37 | } 38 | 39 | 40 | public extension Bundle { 41 | 42 | /// Retrieves the `infoDictionary` dictionary inside Bundle and 43 | /// returns the value accessed with the key `CFBundleName`. 44 | var appName: String { 45 | guard let infoDictionary = Bundle.main.infoDictionary else { return "unknown" } 46 | guard let name = infoDictionary["CFBundleName"] as? String else { return "unknown" } 47 | return name 48 | } 49 | 50 | /// Retrieves the `infoDictionary` dictionary inside Bundle and 51 | /// returns the value accessed with the key `CFBundleIdentifier`. 52 | var bundleIdentifier: String { 53 | guard let infoDictionary = Bundle.main.infoDictionary else { return "unknown" } 54 | guard let bundleIdentifier = infoDictionary["CFBundleIdentifier"] as? String else { return "unknown" } 55 | return bundleIdentifier 56 | } 57 | 58 | /// Retrieves the `infoDictionary` dictionary inside Bundle and 59 | /// returns the value accessed with the key `CFBundleVersion`. 60 | var buildNumber: String { 61 | guard let infoDictionary = Bundle.main.infoDictionary else { return "unknown" } 62 | guard let buildNumber = infoDictionary["CFBundleVersion"] as? String else { return "unknown" } 63 | return buildNumber 64 | } 65 | 66 | /// Retrieves the `infoDictionary` dictionary inside Bundle and 67 | /// returns the value accessed with the key `CFBundleShortVersionString`. 68 | var versionNumber: String { 69 | guard let infoDictionary = Bundle.main.infoDictionary else { return "unknown" } 70 | guard let versionNumber = infoDictionary["CFBundleShortVersionString"] as? String else { return "unknwon" } 71 | return versionNumber 72 | } 73 | 74 | /// Retrieves the `infoDictionary` dictionary inside Bundle, retrieves 75 | /// the value accessed with the key `CFBundleShortVersionString` and parses 76 | /// it to return the version major number. 77 | /// 78 | /// - Returns: the version number of the Xcode project as a whole(e.g. 1.0.3) 79 | var versionMajorNumber: String { 80 | guard let infoDictionary = Bundle.main.infoDictionary else { return "unknown" } 81 | guard let versionNumber = infoDictionary["CFBundleShortVersionString"] as? String else { return "unknwon" } 82 | return versionNumber.components(separatedBy: ".")[0] 83 | } 84 | 85 | /// Retrieves the `infoDictionary` dictionary inside Bundle, retrieves 86 | /// the value accessed with the key `CFBundleShortVersionString` and parses 87 | /// it to return the version minor number. 88 | var versionMinorNumber: String { 89 | guard let infoDictionary = Bundle.main.infoDictionary else { return "unknown" } 90 | guard let versionNumber = infoDictionary["CFBundleShortVersionString"] as? String else { return "unknwon" } 91 | return versionNumber.components(separatedBy: ".")[1] 92 | } 93 | 94 | /// Retrieves the `infoDictionary` dictionary inside Bundle, retrieves 95 | /// the value accessed with the key `CFBundleShortVersionString` and parses 96 | /// it to return the version patch number. 97 | var versionPatchNumber: String { 98 | guard let infoDictionary = Bundle.main.infoDictionary else { return "unknown" } 99 | guard let versionNumber = infoDictionary["CFBundleShortVersionString"] as? String else { return "unknwon" } 100 | return versionNumber.components(separatedBy: ".")[2] 101 | } 102 | 103 | /// Retrieves the `infoDictionary` dictionary inside Bundle, and retrieves 104 | /// all the values inside it 105 | var getInfoDictionary: [String: Any] { 106 | guard let infoDictionary = Bundle.main.infoDictionary else { return [:] } 107 | return infoDictionary 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/CALayer+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 9/7/18. 6 | // 7 | #if !os(macOS) 8 | import UIKit 9 | 10 | public extension CALayer { 11 | 12 | /// Adding view border 13 | /// 14 | /// - Parameters: 15 | /// - edge: UIRectEdge 16 | /// - color: color of the border 17 | /// - thickness: thickness of the border 18 | func addBorder(edge: UIRectEdge, color: UIColor? = .darkGray, thickness: CGFloat? = 1.0) { 19 | 20 | guard let color = color else { return } 21 | guard let thickness = thickness else { return } 22 | 23 | let border = CALayer(); 24 | switch edge { 25 | case UIRectEdge.top: 26 | border.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: thickness) 27 | break 28 | case UIRectEdge.bottom: 29 | border.frame = CGRect(x:0, y:self.frame.height - thickness, width:self.frame.width, height:thickness) 30 | break 31 | case UIRectEdge.left: 32 | border.frame = CGRect(x:0, y:0, width: thickness, height: self.frame.height) 33 | break 34 | case UIRectEdge.right: 35 | border.frame = CGRect(x:self.frame.width - thickness, y: 0, width: thickness, height:self.frame.height) 36 | break 37 | default: 38 | break 39 | } 40 | 41 | border.backgroundColor = color.cgColor; 42 | addSublayer(border) 43 | } 44 | 45 | /// Add rounded corner to any corner 46 | /// 47 | /// - Parameters: 48 | /// - corners: [CACornerMask] 49 | /// - radius: corner radius 50 | func roundCorners(corners: CACornerMask, radius: CGFloat){ 51 | self.masksToBounds = true 52 | self.cornerRadius = radius 53 | if #available(iOS 11.0, *) { 54 | self.maskedCorners = corners 55 | } 56 | } 57 | } 58 | #endif 59 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/CLLocationCoordinate2D+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLLocationCoordinate2D+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 04/02/2022. 6 | // 7 | 8 | import CoreLocation 9 | 10 | public extension CLLocationCoordinate2D { 11 | static func coordinate(latitude: Double, longitude: Double) -> CLLocationCoordinate2D { 12 | .init(latitude: latitude, longitude: longitude) 13 | } 14 | } 15 | 16 | extension CLLocationCoordinate2D: Equatable { 17 | public static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool { 18 | lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/CheckedContinuation+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CheckedContinuation+Extension.swift 3 | // 4 | // 5 | // Created by Abdullah Alhaider on 02/07/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension CheckedContinuation where T == Void { 11 | func resume(withErrorIfExist error: E?) { 12 | if let error = error { 13 | resume(throwing: error) 14 | } else { 15 | resume() 16 | } 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/Color+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 19/03/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @available(iOS 13.0, *) 11 | public extension Color { 12 | 13 | func uiColor() -> UIColor { 14 | 15 | if #available(iOS 14.0, *) { 16 | return UIColor(self) 17 | } 18 | 19 | let components = self.components() 20 | return UIColor(red: components.r, green: components.g, blue: components.b, alpha: components.a) 21 | } 22 | 23 | private func components() -> (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) { 24 | 25 | let scanner = Scanner(string: self.description.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)) 26 | var hexNumber: UInt64 = 0 27 | var r: CGFloat = 0.0, g: CGFloat = 0.0, b: CGFloat = 0.0, a: CGFloat = 0.0 28 | 29 | let result = scanner.scanHexInt64(&hexNumber) 30 | if result { 31 | r = CGFloat((hexNumber & 0xff000000) >> 24) / 255 32 | g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255 33 | b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255 34 | a = CGFloat(hexNumber & 0x000000ff) / 255 35 | } 36 | return (r, g, b, a) 37 | } 38 | } 39 | 40 | @available(iOS 14.0, *) 41 | public extension Color { 42 | 43 | /// Converts a SwiftUI Color to a hexadecimal string representation. 44 | /// 45 | /// - Returns: A String representing the color in hexadecimal format. 46 | /// Returns nil if the color cannot be converted to RGB. 47 | func toHexString() -> String? { 48 | // Convert the Color to UIColor 49 | let uiColor = UIColor(self) 50 | 51 | // Extract RGBA components from UIColor 52 | var red: CGFloat = 0 53 | var green: CGFloat = 0 54 | var blue: CGFloat = 0 55 | var alpha: CGFloat = 0 56 | 57 | guard uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) else { 58 | // Return nil if unable to extract RGBA components 59 | return nil 60 | } 61 | 62 | // Format the components as a hexadecimal string 63 | return String( 64 | format: "#%02lX%02lX%02lX", 65 | lround(Double(red) * 255), 66 | lround(Double(green) * 255), 67 | lround(Double(blue) * 255) 68 | ) 69 | } 70 | 71 | // Initialize Color from Hex String 72 | init(hex: String) { 73 | let hexString = hex.trimmingCharacters(in: .whitespacesAndNewlines) 74 | let scanner = Scanner(string: hexString) 75 | 76 | if hexString.hasPrefix("#") { 77 | scanner.currentIndex = hexString.index(after: hexString.startIndex) 78 | } 79 | 80 | var color: UInt64 = 0 81 | scanner.scanHexInt64(&color) 82 | 83 | let mask = 0x000000FF 84 | let r = Int(color >> 16) & mask 85 | let g = Int(color >> 8) & mask 86 | let b = Int(color) & mask 87 | 88 | let red = CGFloat(r) / 255 89 | let green = CGFloat(g) / 255 90 | let blue = CGFloat(b) / 255 91 | 92 | self.init(red: red, green: green, blue: blue) 93 | } 94 | 95 | 96 | /// Determines if the color is perceived as dark or light. 97 | /// This method calculates the relative luminance of the color in the sRGB space 98 | /// and compares it against a threshold to decide if the color is dark. 99 | /// 100 | /// - Returns: A Boolean value indicating whether the color is dark (`true`) or light (`false`). 101 | func isDarkColor() -> Bool { 102 | // Extract RGB components from the color 103 | guard let components = UIColor(self).cgColor.components, components.count >= 3 else { 104 | return false 105 | } 106 | 107 | // Assign RGB components to respective variables 108 | let red = components[0] 109 | let green = components[1] 110 | let blue = components[2] 111 | 112 | // Calculate luminance for each color component. 113 | // The formula converts the linear RGB values to sRGB, 114 | // which better represents human perception of brightness. 115 | let redLuminance = red < 0.03928 ? red / 12.92 : pow((red + 0.055) / 1.055, 2.4) 116 | let greenLuminance = green < 0.03928 ? green / 12.92 : pow((green + 0.055) / 1.055, 2.4) 117 | let blueLuminance = blue < 0.03928 ? blue / 12.92 : pow((blue + 0.055) / 1.055, 2.4) 118 | 119 | // Calculate the overall luminance of the color using the standard coefficients. 120 | // These coefficients (0.2126, 0.7152, 0.0722) correspond to the human eye's 121 | // sensitivity to red, green, and blue light, respectively. 122 | let luminance = 0.2126 * redLuminance + 0.7152 * greenLuminance + 0.0722 * blueLuminance 123 | 124 | // Compare the luminance against a threshold to determine if the color is dark. 125 | // The threshold value of 0.5 can be adjusted based on specific requirements. 126 | return luminance < 0.5 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/Date+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 18/05/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Cases of the date formate 11 | public enum DateFormat: String { 12 | /// MMMMddYYYYWithTime: OCT 10, 2019 at 12:32 am 13 | case MMMMddYYYYWithTime = "MMMM dd, yyyy 'at' h:mm a" 14 | /// ddmmyyyyWithTime: 23/10/2019 at 12:32 am 15 | case ddmmyyyyWithTime = "dd/MM/yyyy 'at' h:mm a" 16 | case requestDateFormat = "dd-MM-yyyy hh:mm:ss.SSSSS" 17 | case YYYYMMDD = "yyyyMMdd" 18 | case DDMMMYYYY = "dd MMM yyyy" 19 | case DateWithTimeZone = "yyyy-MM-dd'T'HH:mm:Ss.SSSZ" // Time zone date 20 | case MMMYYY = "MMM yyy" /// For date used in view bills 21 | case MMM = "MMM" 22 | /* 23 | You can add as many format as you want 24 | and if you not familiar with other date format you can use this website 25 | to pick your best format http://nsdateformatter.com 26 | */ 27 | } 28 | 29 | public extension Date { 30 | 31 | /// Getting the current date in selected format 32 | /// 33 | /// - Parameter format: date format 34 | /// - Returns: the current date in the selected format 35 | func getCurrentDate(format: DateFormat = .DDMMMYYYY) -> String { 36 | let date = Date() 37 | let formatter = DateFormatter() 38 | formatter.locale = Locale(identifier: "en_US_POSIX") 39 | formatter.dateFormat = format.rawValue // from enum dateFormat 40 | let formatedDate = formatter.string(from: date) 41 | 42 | return formatedDate 43 | } 44 | 45 | /// Getting the current date by passing your custom date format 46 | /// 47 | /// - Parameter formatToUse: your custom date format 48 | /// - Returns: current date 49 | func getCurrentDateUsingThisFormat(_ formatToUse: String = "dd/MM/yyyy 'at' h:mm a") -> String { 50 | let date = Date() 51 | let formatter = DateFormatter() 52 | formatter.locale = Locale(identifier: "en_US_POSIX") 53 | formatter.dateFormat = formatToUse 54 | let formatedDate = formatter.string(from: date) 55 | 56 | return formatedDate 57 | } 58 | 59 | /// Calculating the time interval since a given date 60 | /// 61 | /// - Parameter date: Date() 62 | /// - Returns: time interval 63 | func timeSince(_ date: Date) -> TimeInterval { 64 | let time = Date() 65 | return time.timeIntervalSince(date) 66 | } 67 | 68 | /// https://developer.apple.com/documentation/foundation/relativedatetimeformatter 69 | func relativeTime( 70 | in locale: Locale = .current, 71 | unitsStyle: RelativeDateTimeFormatter.UnitsStyle = .short, 72 | relativeTo otherDate: Date = Date() 73 | ) -> String { 74 | let formatter = RelativeDateTimeFormatter() 75 | formatter.locale = locale 76 | formatter.unitsStyle = unitsStyle 77 | return formatter.localizedString(for: self, relativeTo: otherDate) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/Dictionary+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 18/05/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension Dictionary { 11 | 12 | /// Converting the dictionary to string 13 | var json: String { 14 | do { 15 | let jsonData = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) 16 | return String(bytes: jsonData, encoding: String.Encoding.utf8) ?? "Not a valid JSON" 17 | } catch { 18 | return "Not a valid JSON" 19 | } 20 | } 21 | 22 | /// Updates the value stored in the dictionary for the given key, or 23 | /// adds a new key-value pair if the key does not exist. 24 | /// 25 | /// ``` 26 | /// var names = ["Abdullah": "Tariq", "Ali": "Abdullah"] 27 | /// var newNames = ["Ahmed": "Abdulaziz", "M": "Sara"] 28 | /// names.update(with: newNames) 29 | /// print(names) // ["Ahmed": "Abdulaziz", "Abdullah": "Tariq", "M": "Sara", "Ali": "Abdullah"] 30 | /// ``` 31 | /// 32 | /// - Parameter newDictionary: The new dictionary you want to add it to the old dictionary 33 | mutating func update(with newDictionary: Dictionary) { 34 | newDictionary.forEach({self.updateValue($1, forKey: $0)}) 35 | } 36 | 37 | /// JSON Data from dictionary. 38 | /// 39 | /// - Parameter prettify: set true to prettify data (default is false). 40 | /// - Returns: optional JSON Data (if applicable). 41 | func jsonData(prettify: Bool = false) -> Data? { 42 | guard JSONSerialization.isValidJSONObject(self) else { 43 | return nil 44 | } 45 | let options = (prettify == true) ? JSONSerialization.WritingOptions.prettyPrinted : JSONSerialization.WritingOptions() 46 | return try? JSONSerialization.data(withJSONObject: self, options: options) 47 | } 48 | } 49 | 50 | public extension Dictionary where Value: Equatable { 51 | 52 | /// Returns an array of all keys that have the given value in dictionary. 53 | /// 54 | /// let dict = ["key1": "value1", "key2": "value1", "key3": "value2"] 55 | /// dict.keys(forValue: "value1") -> ["key1", "key2"] 56 | /// dict.keys(forValue: "value2") -> ["key3"] 57 | /// dict.keys(forValue: "value3") -> [] 58 | /// 59 | /// - Parameter value: Value for which keys are to be fetched. 60 | /// - Returns: An array containing keys that have the given value. 61 | func keys(forValue value: Value) -> [Key] { 62 | return keys.filter { self[$0] == value } 63 | } 64 | } 65 | 66 | public extension Dictionary where Key == String, Value == Any { 67 | 68 | /// Appinding a dictionary of type [String: Any] to another dictionary with same type 69 | /// 70 | /// ``` 71 | /// var names: [String: Any] = ["Abdullah": true, "Ali": 1] 72 | /// var newNames = ["Ahmed": "Abdulaziz", "M": "Sara"] 73 | /// names+=newNames 74 | /// print(names) // ["Ali": 1, "Ahmed": "Abdulaziz", "Abdullah": true, "M": "Sara"] 75 | /// ``` 76 | static func += (lhs: inout [String: Any], rhs: [String: Any]) { 77 | rhs.forEach({ lhs[$0] = $1}) 78 | } 79 | } 80 | 81 | public extension Dictionary where Key == String, Value == String { 82 | 83 | /// Appinding a dictionary of [String: String] to another dictionary with same type 84 | /// 85 | /// ``` 86 | /// var names = ["Abdullah": "Tariq", "Ali": "Abdullah"] 87 | /// var newNames = ["Ahmed": "Abdulaziz", "M": "Layan"] 88 | /// names+=newNames 89 | /// print(names) // ["Ahmed": "Abdulaziz", "Abdullah": "Tariq", "M": "Sara", "Ali": "Abdullah"] 90 | /// ``` 91 | static func += (lhs: inout [String: String], rhs: [String: String]) { 92 | rhs.forEach({ lhs[$0] = $1}) 93 | } 94 | } 95 | 96 | // Generic function 97 | // 98 | // func +=(lhs: inout [U:T], rhs: [U:T]) { 99 | // rhs.forEach({ lhs[$0] = $1 }) 100 | // } 101 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/Dispatch+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dispatch+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 18/05/2019. 6 | // 7 | 8 | import Dispatch 9 | 10 | public extension DispatchQueue { 11 | 12 | /// A Boolean value indicating whether the current 13 | /// dispatch queue is the main queue. 14 | static var isMainQueue: Bool { 15 | enum Static { 16 | static var key: DispatchSpecificKey = { 17 | let key = DispatchSpecificKey() 18 | DispatchQueue.main.setSpecific(key: key, value: ()) 19 | return key 20 | }() 21 | } 22 | return DispatchQueue.getSpecific(key: Static.key) != nil 23 | } 24 | } 25 | 26 | public extension DispatchQueue { 27 | 28 | /// Returns a Boolean value indicating whether the current 29 | /// dispatch queue is the specified queue. 30 | /// 31 | /// - Parameter queue: The queue to compare against. 32 | /// - Returns: `true` if the current queue is the specified queue, otherwise `false`. 33 | static func isCurrent(_ queue: DispatchQueue) -> Bool { 34 | let key = DispatchSpecificKey() 35 | 36 | queue.setSpecific(key: key, value: ()) 37 | defer { queue.setSpecific(key: key, value: nil) } 38 | 39 | return DispatchQueue.getSpecific(key: key) != nil 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/Encodable+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Encodable+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 18/05/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension Encodable { 11 | 12 | /// Printing the response json on the consol in pretty printed json 13 | var debugDescription: String { 14 | do { 15 | let encoder = JSONEncoder() 16 | encoder.outputFormatting = .prettyPrinted 17 | let jsonData = try encoder.encode(self) 18 | return String(data: jsonData, encoding: String.Encoding.utf8) ?? "nil" 19 | } catch { 20 | return "nil" 21 | } 22 | } 23 | 24 | /// Converting object to postable [String: Any] 25 | func toDictionary(_ encoder: JSONEncoder = JSONEncoder()) -> [String: Any] { 26 | guard let data = try? encoder.encode(self) else { return [:] } 27 | guard let object = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) else { return [:] } 28 | guard let json = object as? [String: Any] else { return [:] } 29 | return json 30 | } 31 | 32 | /// Converting object to postable array of [[String: Any]] 33 | func toDictionaryArray(_ encoder: JSONEncoder = JSONEncoder()) -> [[String: Any]] { 34 | guard let data = try? encoder.encode(self) else { return [[:]] } 35 | guard let object = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) else { return [[:]] } 36 | guard let jsonArray = object as? [[String: Any]] else { return [[:]] } 37 | return jsonArray 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/Int+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 18/05/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension Int { 11 | 12 | /// Convert Int timeIntervalSince1970 to string date 13 | /// 14 | /// - Parameter format: date format 15 | /// - Returns: string date 16 | func convertToDate(format: DateFormat = .DDMMMYYYY) -> String { 17 | let date = Date(timeIntervalSince1970: TimeInterval(self)) 18 | let dateFormatter = DateFormatter() 19 | dateFormatter.dateFormat = format.rawValue 20 | return dateFormatter.string(from: date) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/JSONDecoder+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONDecoder+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 18/05/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension JSONDecoder { 11 | 12 | /// Decode a given Decodable object from a given url 13 | /// 14 | /// - Parameters: 15 | /// - type: Decodable 16 | /// - url: URL 17 | /// - completion: data 18 | func decode(_ type: T.Type, fromURL url: String, completion: @escaping (T) -> Void) { 19 | guard let url = URL(string: url) else { fatalError("Invalid URL passed.") } 20 | DispatchQueue.global().async { 21 | do { 22 | let data = try Data(contentsOf: url) 23 | let downloadedData = try self.decode(type, from: data) 24 | DispatchQueue.main.async { completion(downloadedData) } 25 | } catch { 26 | print(error.localizedDescription) 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/MKCoordinateSpan+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MKCoordinateSpan+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 03/11/2023. 6 | // 7 | 8 | import MapKit 9 | 10 | public extension MKCoordinateSpan { 11 | 12 | /// Initializes a new MKCoordinateSpan that represents the area covered by the given distance in meters. 13 | /// - Parameters: 14 | /// - distance: The distance in meters for both latitude and longitude spans. 15 | /// - center: The center coordinate for the calculation. 16 | init(distance: CLLocationDistance, center: CLLocationCoordinate2D) { 17 | let oneDegreeOfLatitudeInMeters: CLLocationDistance = 111_32.0 // Average value in meters 18 | let latitudeDelta = distance / oneDegreeOfLatitudeInMeters 19 | 20 | let radiansLatitude = center.latitude * .pi / 180.0 21 | let oneDegreeOfLongitudeInMeters = cos(radiansLatitude) * oneDegreeOfLatitudeInMeters 22 | let longitudeDelta = distance / oneDegreeOfLongitudeInMeters 23 | 24 | self.init(latitudeDelta: latitudeDelta, longitudeDelta: longitudeDelta) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/NSMutableAttributedString+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableAttributedString+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 18/05/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | #if canImport(UIKit) 11 | import UIKit 12 | #endif 13 | 14 | public extension NSMutableAttributedString { 15 | 16 | #if !os(macOS) 17 | /// A neat way to make a combination of bold and normal texts in a single label 18 | /// 19 | /// - Parameters: 20 | /// - text: The text to apply style on it 21 | /// - font: your font 22 | /// - color: your foreground color 23 | /// - Returns: NSMutableAttributedString 24 | @discardableResult 25 | func bold(_ text: String, font: UIFont = UIFont.systemFont(ofSize: 17), color: UIColor = .black) -> NSMutableAttributedString { 26 | let boldString = NSMutableAttributedString(string: text, attributes: [.font: font, .foregroundColor: color]) 27 | append(boldString) 28 | return self 29 | } 30 | #endif 31 | 32 | @discardableResult 33 | func normal(_ text: String) -> NSMutableAttributedString { 34 | let normal = NSAttributedString(string: text) 35 | append(normal) 36 | return self 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/Optional+Extension.swift: -------------------------------------------------------------------------------- 1 | extension Optional { 2 | public var isNil: Bool { 3 | return self == nil 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/Sequence+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sequence+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 19/03/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum SortOrder { 11 | case increasing, decreasing 12 | } 13 | 14 | public extension Sequence { 15 | 16 | func sorted(by keyPath: KeyPath, order: SortOrder = .increasing) -> [Self.Element] { 17 | switch order { 18 | case .increasing: 19 | return self.sorted(by: { $0[keyPath: keyPath] < $1[keyPath: keyPath] }) 20 | case .decreasing: 21 | return self.sorted(by: { $0[keyPath: keyPath] > $1[keyPath: keyPath] }) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/String+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 5/11/18. 6 | // https://developer.apple.com/library/content/documentation/Xcode/Reference/xcode_markup_formatting_ref/ 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | #if canImport(Cocoa) 12 | import Cocoa 13 | #endif 14 | 15 | #if canImport(UIKit) 16 | import UIKit 17 | #endif 18 | 19 | public extension String { 20 | 21 | /// Convert Arabic numbers to English numbers 22 | var toEnglishNumbers: String { 23 | var str = self 24 | let map = ["٠": "0", 25 | "١": "1", 26 | "٢": "2", 27 | "٣": "3", 28 | "٤": "4", 29 | "٥": "5", 30 | "٦": "6", 31 | "٧": "7", 32 | "٨": "8", 33 | "٩": "9"] 34 | map.forEach { str = str.replacingOccurrences(of: $0, with: $1) } 35 | return str 36 | } 37 | 38 | /// Used for change the language 39 | var localized: String { 40 | return NSLocalizedString(self, comment: "") 41 | } 42 | 43 | 44 | /// Readable way to use !text.isEmpty 45 | var isNotEmpty: Bool { 46 | return !self.isEmpty 47 | } 48 | 49 | #if !os(macOS) 50 | /// Shortcut of: UIImage(named: "imageName") 51 | var asImage: UIImage? { 52 | return UIImage(named: self) 53 | } 54 | #elseif os(macOS) 55 | /// Shortcut of: NSImage(named: "imageName") 56 | var asImage: NSImage? { 57 | return NSImage(named: self) 58 | } 59 | #endif 60 | 61 | /// Shortcut for string urls like: URL(string: "http://google.com") 62 | var asURL: URL? { 63 | return URL(string: self) 64 | } 65 | 66 | /// Shortcut for Notification.Name like: Notification.Name(rawValue: "string") 67 | var asNotificationName: Notification.Name { 68 | return Notification.Name(rawValue: self) 69 | } 70 | 71 | #if !os(macOS) 72 | /// Shortcut for `UIStoryboard(name: "UIStoryboard", bundle: nil)` 73 | var asStoryboard: UIStoryboard { 74 | return UIStoryboard(name: self, bundle: nil) 75 | } 76 | #elseif os(macOS) 77 | /// Shortcut for `NSStoryboard(name: "NSStoryboard", bundle: nil)` 78 | var asStoryboard: NSStoryboard { 79 | return NSStoryboard(name: self, bundle: nil) 80 | } 81 | #endif 82 | 83 | /// Checking if string is empty and does not contain white spaces 84 | var isEmptyAtAll: Bool { 85 | if self.isEmpty && !isWhiteSpaceOnly { 86 | return true 87 | } 88 | return false 89 | } 90 | 91 | /// Return only the digits from a string 92 | var onlyDigits: String { 93 | let set = NSCharacterSet.decimalDigits.inverted 94 | return self.components(separatedBy: set).joined(separator: "") 95 | } 96 | 97 | /// Return same string withot whitespaces or newlines 98 | var trimmed: String { 99 | return self.trimmingCharacters(in: .whitespacesAndNewlines) 100 | } 101 | 102 | /// Return true if the string matched an email format like "cs.alhaider@gmail.com" 103 | var isValidEmail: Bool { 104 | let emailFormat = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" // This is a regular expression 105 | return self.matches(emailFormat) 106 | } 107 | 108 | /// Return true if the string has only numbers "0123456789". 109 | var isValidNumber: Bool { 110 | let numberFormat = "^[0-9]*$" 111 | return self.matches(numberFormat) 112 | } 113 | 114 | /// Return true if the string has minimum 8 characters, and at least one uppercase letter, and one lowercase letter and one number 115 | /// , You can see more on http://regexlib.com/Search.aspx?k=password 116 | var isValidPassword: Bool { 117 | let passwordFormat = "^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$" 118 | return self.matches(passwordFormat) 119 | } 120 | 121 | /// Validating a string whether it is hexadecimal color or not using regular expression 122 | var isValidHex: Bool { 123 | let hexadecimalFormat = "^#(?:[0-9a-fA-F]{3}){1,2}$" 124 | return self.matches(hexadecimalFormat) 125 | } 126 | 127 | /// Return true if the string is a valid url 128 | var isValidUrl: Bool { 129 | return URL(string: self) != nil 130 | } 131 | 132 | /// Checking for whitespace 133 | var isWhiteSpaceOnly: Bool { 134 | if self.isEmpty { 135 | return false 136 | } 137 | let emptyFormat = "\\s*" 138 | return self.matches(emptyFormat) 139 | } 140 | 141 | /// Checking if string contain only 0s 142 | var isStringOnlyZeros: Bool { 143 | let zeroFormat = "^[0٠]+$" 144 | return self.matches(zeroFormat) 145 | } 146 | 147 | /// String decoded from base64 (if applicable). 148 | /// 149 | /// "SGVsbG8gV29ybGQh".base64Decoded = Optional("Hello World!") 150 | var base64Decoded: String? { 151 | guard let decodedData = Data(base64Encoded: self) else { return nil } 152 | return String(data: decodedData, encoding: .utf8) 153 | } 154 | 155 | /// String encoded in base64 (if applicable). 156 | /// 157 | /// "Hello World!".base64Encoded -> Optional("SGVsbG8gV29ybGQh") 158 | var base64Encoded: String? { 159 | let plainData = data(using: .utf8) 160 | return plainData?.base64EncodedString() 161 | } 162 | 163 | /// Readable string from a URL string. 164 | /// 165 | /// "it's%20easy%20to%20decode%20strings".urlDecoded -> "it's easy to decode strings" 166 | var urlDecoded: String { 167 | return removingPercentEncoding ?? self 168 | } 169 | 170 | /// URL escaped string. 171 | /// 172 | /// "it's easy to encode strings".urlHostAllowed -> "it's%20easy%20to%20encode%20strings" 173 | var urlHostAllowed: String { 174 | return addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? self 175 | } 176 | 177 | /// URL escaped string to add percent encoding using .urlQueryAllowed 178 | var urlQueryAllowed: String { 179 | return addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? self 180 | } 181 | 182 | } 183 | 184 | // MARK: - // -------------------------------- Methods ------------------------------------- // 185 | 186 | public extension String { 187 | 188 | /// Helper method to extract all URLs from a given text 189 | /// 190 | /// - Returns: Array of string URLs or empty [] 191 | func extractAllURLs() -> [URL] { 192 | let input = self // "This is a test with the URL https://www.hackingwithswift.com to be detected." 193 | if let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) { 194 | let matches = detector.matches(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count)) 195 | return matches.compactMap(\.url) 196 | } else { 197 | return [] 198 | } 199 | } 200 | 201 | /// Replacing string with another string "aaa" => "ttt" 202 | /// 203 | /// - Parameters: 204 | /// - string: orignal string 205 | /// - replacement: replacment string 206 | /// - Returns: replaced text 207 | func replace(string: String, replacement: String) -> String { 208 | return self.replacingOccurrences(of: string, with: replacement, options: String.CompareOptions.literal, range: nil) 209 | } 210 | 211 | /// Removing the white space in any string by calling removeWhitespace() after a string value 212 | /// 213 | /// - Returns: String with no white space in 214 | func removeWhitespace() -> String { 215 | return self.replace(string: " ", replacement: "") 216 | } 217 | 218 | /// Validate for if regex matches 219 | /// 220 | /// - Parameter regex: regular expression 221 | /// - Returns: true if matches the given regex 222 | func matches(_ regex: String) -> Bool { 223 | return self.range(of: regex, options: .regularExpression) != nil 224 | } 225 | 226 | #if !os(macOS) 227 | /// Open url using the string 228 | func openUrl() { 229 | guard let url = URL(string: self) else { return } 230 | if #available(iOS 10.0, *) { 231 | UIApplication.shared.open(url, options: [:]) 232 | } else { 233 | UIApplication.shared.openURL(url) 234 | } 235 | } 236 | 237 | /// Open in messages if the string is valid number 238 | /// 239 | /// - Parameter message: message body 240 | func openInMessagess(message: String) { 241 | if self.isValidNumber { 242 | "sms:\(self)&body=\(message.urlQueryAllowed)".openUrl() 243 | } 244 | } 245 | 246 | /// Calling ussd number 247 | func call() { 248 | let urlString = self.urlHostAllowed 249 | let url = "tel://\(urlString)" 250 | url.openUrl() 251 | } 252 | #endif 253 | 254 | /// Counting the length and returning a boolen value 255 | /// 256 | /// - Parameters: 257 | /// - min: minmum length 258 | /// - max: maxmum length 259 | /// - Returns: true if the length is more than or equel the `min` and length is less than or equel the `max` 260 | func lengthIsBetween(min: Int, max: Int) -> Bool { 261 | return (self.count >= min) && (self.count <= max) 262 | } 263 | 264 | /// Formattting Phone number to be like `"051-223-4432"` insted of `"0512234432"` 265 | /// 266 | /// - Parameters: 267 | /// - pattern: Any look you want like: `"###-###-####"` 268 | /// - replacmentCharacter: The same character you used in above `#` 269 | /// - Returns: formated string number 270 | func applyPatternOnNumbers(pattern: String, replacmentCharacter: Character) -> String { 271 | var pureNumber = self.replace(string: "[^0-9]", replacement: "") 272 | for index in 0 ..< pattern.count { 273 | guard index < pureNumber.count else { return pureNumber } 274 | let stringIndex = String.Index(utf16Offset: index, in: self) 275 | let patternCharacter = pattern[stringIndex] 276 | guard patternCharacter != replacmentCharacter else { continue } 277 | pureNumber.insert(patternCharacter, at: stringIndex) 278 | } 279 | return pureNumber 280 | } 281 | 282 | /// Retrieve the value from url string 283 | /// 284 | /// Example: 285 | /// ``` 286 | /// let url = "http://mysite3994.com?test1=blah&test2=blahblah&errorCode=5544" 287 | /// let errorCode = url.getQueryStringParameter(param: "errorCode") // -> It will return "5544" 288 | /// ``` 289 | /// 290 | /// - Parameter param: param that included in the string url 291 | /// - Returns: the value, String 292 | func getQueryStringParameter(param: String) -> String? { 293 | guard let url = URLComponents(string: self) else { return nil } 294 | return url.queryItems?.first(where: { $0.name == param })?.value 295 | } 296 | } 297 | 298 | // MARK:- Version comparison methods 299 | public extension String { 300 | 301 | /// Inner comparison helper to handle same versions with different length. (Ex: "1.0.0" & "1.0") 302 | private func compare(toVersion targetVersion: String) -> ComparisonResult { 303 | 304 | let versionDelimiter = "." 305 | var result: ComparisonResult = .orderedSame 306 | var versionComponents = components(separatedBy: versionDelimiter) 307 | var targetComponents = targetVersion.components(separatedBy: versionDelimiter) 308 | let spareCount = versionComponents.count - targetComponents.count 309 | 310 | if spareCount == 0 { 311 | result = compare(targetVersion, options: .numeric) 312 | } else { 313 | let spareZeros = repeatElement("0", count: abs(spareCount)) 314 | if spareCount > 0 { 315 | targetComponents.append(contentsOf: spareZeros) 316 | } else { 317 | versionComponents.append(contentsOf: spareZeros) 318 | } 319 | result = versionComponents.joined(separator: versionDelimiter) 320 | .compare(targetComponents.joined(separator: versionDelimiter), options: .numeric) 321 | } 322 | return result 323 | } 324 | 325 | func isVersion(equalTo targetVersion: String) -> Bool { 326 | return compare(toVersion: targetVersion) == .orderedSame 327 | } 328 | 329 | func isVersion(greaterThan targetVersion: String) -> Bool { 330 | return compare(toVersion: targetVersion) == .orderedDescending 331 | } 332 | 333 | func isVersion(greaterThanOrEqualTo targetVersion: String) -> Bool { 334 | return compare(toVersion: targetVersion) != .orderedAscending 335 | } 336 | 337 | func isVersion(lessThan targetVersion: String) -> Bool { 338 | return compare(toVersion: targetVersion) == .orderedAscending 339 | } 340 | 341 | func isVersion(lessThanOrEqualTo targetVersion: String) -> Bool { 342 | return compare(toVersion: targetVersion) != .orderedDescending 343 | } 344 | } 345 | 346 | public extension String { 347 | // Initialize Color from Hex String 348 | func toColor() -> Color { 349 | let hex = self.trimmingCharacters(in: .whitespacesAndNewlines) 350 | let scanner = Scanner(string: hex) 351 | 352 | if hex.hasPrefix("#") { 353 | scanner.currentIndex = hex.index(after: hex.startIndex) 354 | } 355 | 356 | var color: UInt64 = 0 357 | scanner.scanHexInt64(&color) 358 | 359 | let mask = 0x000000FF 360 | let r = Int(color >> 16) & mask 361 | let g = Int(color >> 8) & mask 362 | let b = Int(color) & mask 363 | 364 | let red = CGFloat(r) / 255 365 | let green = CGFloat(g) / 255 366 | let blue = CGFloat(b) / 255 367 | 368 | return Color(red: red, green: green, blue: blue) 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/TimeInterval+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeInterval+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 23/08/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension TimeInterval { 11 | 12 | static var oneHour: TimeInterval { 13 | return 3600 14 | } 15 | 16 | static var twoHour: TimeInterval { 17 | return oneHour * 2 18 | } 19 | 20 | static var oneDay: TimeInterval { 21 | return oneHour * 24 22 | } 23 | 24 | static var twoDays: TimeInterval { 25 | return oneDay * 2 26 | } 27 | 28 | static var threeDays: TimeInterval { 29 | return oneDay * 3 30 | } 31 | 32 | static var oneWeek: TimeInterval { 33 | return oneDay * 7 34 | } 35 | 36 | static var oneMonth: TimeInterval { 37 | return oneDay * 30 38 | } 39 | 40 | static var oneYear: TimeInterval { 41 | return oneDay * 365 42 | } 43 | 44 | static func customHour(hour: Int) -> TimeInterval { 45 | return oneHour * Double(hour) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/UIApplication+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 18/05/2019. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | public extension UIApplication { 12 | 13 | /// Getting the top controller 14 | /// 15 | /// - Parameter controller: UIViewController 16 | /// - Returns: the top viewController currently displayed 17 | class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? { 18 | if let navigationController = controller as? UINavigationController { 19 | return topViewController(controller: navigationController.visibleViewController) 20 | } 21 | if let tabController = controller as? UITabBarController { 22 | if let selected = tabController.selectedViewController { 23 | return topViewController(controller: selected) 24 | } 25 | } 26 | if let presented = controller?.presentedViewController { 27 | return topViewController(controller: presented) 28 | } 29 | return controller 30 | } 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/UIButton+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 5/11/18. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | public enum ButtonImagePosition { 12 | case left 13 | case right 14 | } 15 | 16 | public extension UIButton { 17 | 18 | /// Shorter way to setTitle 19 | func title(_ title: String, state: UIControl.State = .normal) { 20 | setTitle(title, for: state) 21 | } 22 | 23 | /// Shorter way to setTitleColor 24 | func titleColor(color: UIColor, state: UIControl.State = .normal) { 25 | setTitleColor(color, for: state) 26 | } 27 | 28 | /// Shorter way to setImage 29 | func withImage(_ image: UIImage, state: UIControl.State = .normal) { 30 | setImage(image, for: state) 31 | } 32 | 33 | /// Sets image insets with conformance to user interface direction 34 | /// 35 | /// - Parameters: 36 | /// - insets: Image insets 37 | /// - direction: User interface direction 38 | func setImageEdgeInsets(_ insets: UIEdgeInsets, direction: UIUserInterfaceLayoutDirection) { 39 | if UIApplication.shared.userInterfaceLayoutDirection != direction { 40 | imageEdgeInsets = UIEdgeInsets(top: insets.top, left: insets.right, bottom: insets.bottom, right: insets.left) 41 | } else { 42 | imageEdgeInsets = insets 43 | } 44 | } 45 | 46 | /// Sets title insets with conformance to user interface direction 47 | /// 48 | /// - Parameters: 49 | /// - insets: Title insets 50 | /// - direction: User interface direction 51 | func setTitleEdgeInsets(_ insets: UIEdgeInsets, direction: UIUserInterfaceLayoutDirection) { 52 | if UIApplication.shared.userInterfaceLayoutDirection != direction { 53 | titleEdgeInsets = UIEdgeInsets(top: insets.top, left: insets.right, bottom: insets.bottom, right: insets.left) 54 | } else { 55 | titleEdgeInsets = insets 56 | } 57 | } 58 | 59 | /// Sets position of image 60 | /// 61 | /// - Parameters: 62 | /// - position: Image position 63 | func setImagePosition(_ position: ButtonImagePosition) { 64 | switch position { 65 | case .left: semanticContentAttribute = UIApplication.shared 66 | .userInterfaceLayoutDirection == .rightToLeft ? .forceRightToLeft : .forceLeftToRight 67 | case .right: semanticContentAttribute = UIApplication.shared 68 | .userInterfaceLayoutDirection == .rightToLeft ? .forceLeftToRight : .forceRightToLeft 69 | } 70 | } 71 | 72 | /// Adding under line to button text 73 | func addUnderline() { 74 | guard let text = self.titleLabel?.text else { return } 75 | 76 | let attributedString = NSMutableAttributedString(string: text) 77 | attributedString.addAttribute(NSAttributedString.Key.underlineStyle, 78 | value: NSUnderlineStyle.single.rawValue, 79 | range: NSRange(location: 0, length: text.count)) 80 | 81 | self.setAttributedTitle(attributedString, for: .normal) 82 | } 83 | 84 | /// Multible cases to animate the UIButton 85 | enum UIButtonAnimation { 86 | /// Will change the text color and animate if the duration > 0 87 | case changeTextColor(to: UIColor, duration: TimeInterval) 88 | } 89 | 90 | /// Implimntation for all cases in `UIButtonAnimation` 91 | /// 92 | /// - Parameter animation: UIButtonAnimation 93 | func buttonAnimation(_ animation: UIButtonAnimation) { 94 | switch animation { 95 | case .changeTextColor(let newColor, let duration): 96 | UIView.animate(withDuration: duration) { 97 | self.titleColor(color: newColor) 98 | } 99 | } 100 | } 101 | } 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/UICollectionView+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 18/05/2019. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | public extension UICollectionView { 12 | 13 | /// Index path of last item in collectionView. 14 | var indexPathForLastItem: IndexPath? { 15 | return indexPathForLastItem(inSection: lastSection) 16 | } 17 | 18 | /// Index of last section in collectionView. 19 | var lastSection: Int { 20 | return numberOfSections > 0 ? numberOfSections - 1 : 0 21 | } 22 | } 23 | 24 | public extension UICollectionView { 25 | 26 | /// Number of all items in all sections of collectionView. 27 | /// 28 | /// - Returns: The count of all rows in the collectionView. 29 | func numberOfItems() -> Int { 30 | var section = 0 31 | var itemsCount = 0 32 | while section < numberOfSections { 33 | itemsCount += numberOfItems(inSection: section) 34 | section += 1 35 | } 36 | return itemsCount 37 | } 38 | 39 | /// IndexPath for last item in section. 40 | /// 41 | /// - Parameter section: section to get last item in. 42 | /// - Returns: optional last indexPath for last item in section (if applicable). 43 | func indexPathForLastItem(inSection section: Int) -> IndexPath? { 44 | guard section >= 0 else { 45 | return nil 46 | } 47 | guard section < numberOfSections else { 48 | return nil 49 | } 50 | guard numberOfItems(inSection: section) > 0 else { 51 | return IndexPath(item: 0, section: section) 52 | } 53 | return IndexPath(item: numberOfItems(inSection: section) - 1, section: section) 54 | } 55 | 56 | /// Reload data with a completion handler. 57 | /// 58 | /// - Parameter completion: completion handler to run after reloadData finishes. 59 | func reloadData(_ completion: @escaping () -> Void) { 60 | UIView.animate(withDuration: 0, animations: { 61 | self.reloadData() 62 | }, completion: { _ in 63 | completion() 64 | }) 65 | } 66 | 67 | /// Dequeue reusable UICollectionViewCell using class name. 68 | /// 69 | /// - Parameters: 70 | /// - name: UICollectionViewCell type. 71 | /// - indexPath: location of cell in collectionView. 72 | /// - Returns: UICollectionViewCell object with associated class name. 73 | func dequeueReusableCell(withClass name: T.Type, for indexPath: IndexPath) -> T { 74 | guard let cell = dequeueReusableCell(withReuseIdentifier: String(describing: name), for: indexPath) as? T else { 75 | return T() 76 | } 77 | return cell 78 | } 79 | 80 | /// Dequeue reusable UICollectionReusableView using class name. 81 | /// 82 | /// - Parameters: 83 | /// - kind: the kind of supplementary view to retrieve. This value is defined by the layout object. 84 | /// - name: UICollectionReusableView type. 85 | /// - indexPath: location of cell in collectionView. 86 | /// - Returns: UICollectionReusableView object with associated class name. 87 | func dequeueReusableSupplementaryView(ofKind kind: String, withClass name: T.Type, for indexPath: IndexPath) -> T { 88 | guard let cell = dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: String(describing: name), for: indexPath) as? T else { 89 | return T() 90 | } 91 | return cell 92 | } 93 | 94 | /// Register UICollectionReusableView using class name. 95 | /// 96 | /// - Parameters: 97 | /// - kind: the kind of supplementary view to retrieve. This value is defined by the layout object. 98 | /// - name: UICollectionReusableView type. 99 | func register(supplementaryViewOfKind kind: String, withClass name: T.Type) { 100 | register(T.self, forSupplementaryViewOfKind: kind, withReuseIdentifier: String(describing: name)) 101 | } 102 | 103 | /// Register UICollectionViewCell using class name. 104 | /// 105 | /// - Parameters: 106 | /// - nib: Nib file used to create the collectionView cell. 107 | /// - name: UICollectionViewCell type. 108 | func register(nib: UINib?, forCellWithClass name: T.Type) { 109 | register(nib, forCellWithReuseIdentifier: String(describing: name)) 110 | } 111 | 112 | /// Register UICollectionViewCell using class name. 113 | /// 114 | /// - Parameter name: UICollectionViewCell type. 115 | func register(cellWithClass name: T.Type) { 116 | register(T.self, forCellWithReuseIdentifier: String(describing: name)) 117 | } 118 | 119 | /// Register UICollectionReusableView using class name. 120 | /// 121 | /// - Parameters: 122 | /// - nib: Nib file used to create the reusable view. 123 | /// - kind: the kind of supplementary view to retrieve. This value is defined by the layout object. 124 | /// - name: UICollectionReusableView type. 125 | func register(nib: UINib?, forSupplementaryViewOfKind kind: String, withClass name: T.Type) { 126 | register(nib, forSupplementaryViewOfKind: kind, withReuseIdentifier: String(describing: name)) 127 | } 128 | 129 | /// Register UICollectionViewCell with .xib file using only its corresponding class. 130 | /// Assumes that the .xib filename and cell class has the same name. 131 | /// 132 | /// - Parameters: 133 | /// - name: UICollectionViewCell type. 134 | /// - bundleClass: Class in which the Bundle instance will be based on. 135 | func register(nibWithCellClass name: T.Type, at bundleClass: AnyClass? = nil) { 136 | let identifier = String(describing: name) 137 | var bundle: Bundle? 138 | 139 | if let bundleName = bundleClass { 140 | bundle = Bundle(for: bundleName) 141 | } 142 | 143 | register(UINib(nibName: identifier, bundle: bundle), forCellWithReuseIdentifier: identifier) 144 | } 145 | 146 | } 147 | #endif 148 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/UIColor+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 5/11/18. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | public extension UIColor { 12 | 13 | /// Allowing to use hex string 14 | /// 15 | /// - Parameter hexString: hex string like "ffffff" 16 | convenience init(hexString: String) { 17 | 18 | let hexString: String = (hexString as NSString).trimmingCharacters(in: .whitespacesAndNewlines) 19 | let scanner = Scanner(string: hexString as String) 20 | 21 | if hexString.hasPrefix("#") { 22 | scanner.scanLocation = 1 23 | } 24 | var color: UInt32 = 0 25 | scanner.scanHexInt32(&color) 26 | 27 | let mask = 0x000000FF 28 | let rColor = Int(color >> 16) & mask 29 | let gColor = Int(color >> 8) & mask 30 | let bColor = Int(color) & mask 31 | 32 | let red = CGFloat(rColor) / 255.0 33 | let green = CGFloat(gColor) / 255.0 34 | let blue = CGFloat(bColor) / 255.0 35 | self.init(red: red, green: green, blue: blue, alpha: 1) 36 | } 37 | 38 | /// shortcut for RGB colors 39 | /// 40 | /// - Parameters: 41 | /// - r: red 42 | /// - g: green 43 | /// - b: blue 44 | /// - alpha: alpha = 1 45 | convenience init(red: Int, green: Int, blue: Int, alpha: CGFloat = 1.0) { 46 | self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: alpha) 47 | } 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/UIImage+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 9/7/18. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | public extension UIImage { 12 | 13 | @available(iOS 10.0, *) 14 | var flip: UIImage { 15 | return self.withHorizontallyFlippedOrientation() 16 | } 17 | 18 | func imageWithColor(color1: UIColor) -> UIImage { 19 | UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale) 20 | color1.setFill() 21 | 22 | let context = UIGraphicsGetCurrentContext() 23 | context?.translateBy(x: 0, y: self.size.height) 24 | context?.scaleBy(x: 1.0, y: -1.0) 25 | context?.setBlendMode(CGBlendMode.normal) 26 | 27 | let rect = CGRect(origin: .zero, size: CGSize(width: self.size.width, height: self.size.height)) 28 | context?.clip(to: rect, mask: self.cgImage!) 29 | context?.fill(rect) 30 | 31 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 32 | UIGraphicsEndImageContext() 33 | 34 | return newImage! 35 | } 36 | } 37 | 38 | // Gif image related methods 39 | public extension UIImage { 40 | 41 | /// Loading a gif image from data 42 | /// 43 | /// - Parameter data: data 44 | /// - Returns: UIImage? 45 | class func gif(data: Data) -> UIImage? { 46 | // Create source from data 47 | guard let source = CGImageSourceCreateWithData(data as CFData, nil) else { 48 | print("Source for the image does not exist") 49 | return nil 50 | } 51 | 52 | return UIImage.animatedImageWithSource(source) 53 | } 54 | 55 | class func gif(url: String) -> UIImage? { 56 | // Validate URL 57 | guard let bundleURL = URL(string: url) else { 58 | print("This image named \"\(url)\" does not exist") 59 | return nil 60 | } 61 | 62 | // Validate data 63 | guard let imageData = try? Data(contentsOf: bundleURL) else { 64 | print("Cannot turn image named \"\(url)\" into NSData") 65 | return nil 66 | } 67 | 68 | return gif(data: imageData) 69 | } 70 | 71 | class func gif(name: String) -> UIImage? { 72 | // Check for existance of gif 73 | guard let bundleURL = Bundle.main 74 | .url(forResource: name, withExtension: "gif") else { 75 | print("This image named \"\(name)\" does not exist") 76 | return nil 77 | } 78 | 79 | // Validate data 80 | guard let imageData = try? Data(contentsOf: bundleURL) else { 81 | print("Cannot turn image named \"\(name)\" into NSData") 82 | return nil 83 | } 84 | 85 | return gif(data: imageData) 86 | } 87 | 88 | @available(iOS 9.0, *) 89 | class func gif(asset: String) -> UIImage? { 90 | // Create source from assets catalog 91 | guard let dataAsset = NSDataAsset(name: asset) else { 92 | print("Cannot turn image named \"\(asset)\" into NSDataAsset") 93 | return nil 94 | } 95 | 96 | return gif(data: dataAsset.data) 97 | } 98 | 99 | internal class func delayForImageAtIndex(_ index: Int, source: CGImageSource!) -> Double { 100 | var delay = 0.1 101 | 102 | // Get dictionaries 103 | let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil) 104 | let gifPropertiesPointer = UnsafeMutablePointer.allocate(capacity: 0) 105 | defer { 106 | gifPropertiesPointer.deallocate() 107 | } 108 | let unsafePointer = Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque() 109 | if CFDictionaryGetValueIfPresent(cfProperties, unsafePointer, gifPropertiesPointer) == false { 110 | return delay 111 | } 112 | 113 | let gifProperties: CFDictionary = unsafeBitCast(gifPropertiesPointer.pointee, to: CFDictionary.self) 114 | 115 | // Get delay time 116 | var delayObject: AnyObject = unsafeBitCast( 117 | CFDictionaryGetValue(gifProperties, 118 | Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()), 119 | to: AnyObject.self) 120 | if delayObject.doubleValue == 0 { 121 | delayObject = unsafeBitCast(CFDictionaryGetValue(gifProperties, 122 | Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()), to: AnyObject.self) 123 | } 124 | 125 | if let delayObject = delayObject as? Double, delayObject > 0 { 126 | delay = delayObject 127 | } else { 128 | delay = 0.1 // Make sure they're not too fast 129 | } 130 | 131 | return delay 132 | } 133 | 134 | internal class func gcdForPair(_ lhs: Int?, _ rhs: Int?) -> Int { 135 | var lhs = lhs 136 | var rhs = rhs 137 | // Check if one of them is nil 138 | if rhs == nil || lhs == nil { 139 | if rhs != nil { 140 | return rhs! 141 | } else if lhs != nil { 142 | return lhs! 143 | } else { 144 | return 0 145 | } 146 | } 147 | 148 | // Swap for modulo 149 | if lhs! < rhs! { 150 | let ctp = lhs 151 | lhs = rhs 152 | rhs = ctp 153 | } 154 | 155 | // Get greatest common divisor 156 | var rest: Int 157 | while true { 158 | rest = lhs! % rhs! 159 | 160 | if rest == 0 { 161 | return rhs! // Found it 162 | } else { 163 | lhs = rhs 164 | rhs = rest 165 | } 166 | } 167 | } 168 | 169 | internal class func gcdForArray(_ array: [Int]) -> Int { 170 | if array.isEmpty { 171 | return 1 172 | } 173 | 174 | var gcd = array[0] 175 | 176 | for val in array { 177 | gcd = UIImage.gcdForPair(val, gcd) 178 | } 179 | 180 | return gcd 181 | } 182 | 183 | internal class func animatedImageWithSource(_ source: CGImageSource) -> UIImage? { 184 | let count = CGImageSourceGetCount(source) 185 | var images = [CGImage]() 186 | var delays = [Int]() 187 | 188 | // Fill arrays 189 | for index in 0.. 0 49 | case changeTextColor(to: UIColor, duration: TimeInterval) 50 | } 51 | 52 | /// Implimntation for all cases in `UILabelAnimation` 53 | /// 54 | /// - Parameter animation: UILabelAnimation 55 | func labelAnimation(_ animation: UILabelAnimation) { 56 | switch animation { 57 | case .changeTextColor(let newColor, let duration): 58 | UIView.animate(withDuration: duration) { 59 | self.textColor = newColor 60 | } 61 | } 62 | } 63 | } 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/UINavigationController+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationController+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 18/05/2019. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | extension UINavigationController { 12 | 13 | /// Making the status bar changed from the top view controller 14 | open override var preferredStatusBarStyle: UIStatusBarStyle { 15 | return topViewController?.preferredStatusBarStyle ?? .default 16 | } 17 | } 18 | 19 | public extension UINavigationController { 20 | 21 | /// BAck to spicefic vc in the stack 22 | /// 23 | /// - Parameters: 24 | /// - type: UIViewController 25 | /// - animated: animated 26 | func backTo(_ type: T.Type, animated: Bool = true) { 27 | if let vc = viewControllers.first(where: { $0 is T }) { 28 | popToViewController(vc, animated: animated) 29 | } 30 | } 31 | 32 | /// making the nav bar with gradient image 33 | func updateImageWithGradient(colors: [CGColor]) { 34 | 35 | let navBarHeight = navigationBar.frame.size.height 36 | let statusBarHeight = UIApplication.shared.statusBarFrame.height 37 | let heightAdjustment: CGFloat = 2 38 | let gradientHeight = navBarHeight + statusBarHeight + heightAdjustment 39 | 40 | let bgImage = imageWithGradient(colors: colors, 41 | size: CGSize(width: UIScreen.main.bounds.size.width, 42 | height: gradientHeight)) 43 | guard let image = bgImage else { return } 44 | navigationBar.barTintColor = UIColor(patternImage: image) 45 | } 46 | 47 | /// Create an UIImage 48 | /// 49 | /// - Parameters: 50 | /// - colors: Array of gradient colors 51 | /// - size: CGSize 52 | /// - Returns: UIImage? 53 | private func imageWithGradient(colors: [CGColor], 54 | size: CGSize, locations: [NSNumber] = [0, 1], 55 | startPoint: CGPoint = CGPoint(x: 0.25, y: 0.5), 56 | endPoint: CGPoint = CGPoint(x: 0.75, y: 0.5), 57 | transform: CATransform3D = CATransform3DMakeAffineTransform(CGAffineTransform(a: 0, b: 1, c: -1, d: 0, tx: 1, ty: 0))) -> UIImage? { 58 | 59 | let gradientLayer = CAGradientLayer() 60 | gradientLayer.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) 61 | gradientLayer.colors = colors 62 | gradientLayer.locations = locations 63 | gradientLayer.bounds = view.bounds.insetBy(dx: -0.5 * size.width, dy: -0.5 * size.height) 64 | gradientLayer.startPoint = startPoint 65 | gradientLayer.endPoint = endPoint 66 | gradientLayer.position = self.view.center 67 | gradientLayer.transform = transform 68 | 69 | UIGraphicsBeginImageContext(gradientLayer.bounds.size) 70 | if let context = UIGraphicsGetCurrentContext() { 71 | gradientLayer.render(in: context) 72 | } 73 | let image = UIGraphicsGetImageFromCurrentImageContext() 74 | UIGraphicsEndImageContext() 75 | return image 76 | } 77 | } 78 | #endif 79 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/UIStoryboard+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIStoryboard+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 18/05/2019. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | public extension UIStoryboard { 12 | 13 | /// Instantiate a UIViewController using its class name 14 | /// 15 | /// - Parameter name: UIViewController type 16 | /// - Returns: The view controller corresponding to specified class name 17 | func instantiateVC(withClass name: T.Type) -> T { 18 | return instantiateViewController(withIdentifier: String(describing: name)) as? T ?? T() 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/UITabBarController+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITabBarController+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 8/25/18. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | public extension UITabBarController { 12 | 13 | 14 | /// Creating a navigation controller for UIViewController in UITabBarController 15 | /// 16 | /// - Parameters: 17 | /// - vc: UIViewController() 18 | /// - iconImage: image to use for tab bar image 19 | /// - title: title for the navigation controller and tab bar 20 | /// - Returns: UINavigationController 21 | func creatNavController(for vc: UIViewController, 22 | iconImage: UIImage? = nil, 23 | title: String? = nil, 24 | barTintColor: UIColor? = nil, 25 | isTranslucent: Bool? = nil, 26 | largeTitle: Bool? = nil) -> UINavigationController { 27 | 28 | let navController = UINavigationController(rootViewController: vc) 29 | 30 | if let image = iconImage { 31 | navController.tabBarItem.image = image 32 | } 33 | 34 | if let title = title { 35 | navController.title = title 36 | } 37 | 38 | if let isTranslucent = isTranslucent { 39 | navController.navigationBar.isTranslucent = isTranslucent 40 | } 41 | 42 | if let barTintColor = barTintColor { 43 | navController.navigationBar.barTintColor = barTintColor 44 | } 45 | 46 | if let largeTitle = largeTitle { 47 | if #available(iOS 11.0, *) { 48 | navController.navigationBar.prefersLargeTitles = largeTitle 49 | } 50 | } 51 | 52 | return navController 53 | } 54 | 55 | /// Creating View controller with a navigation controller 56 | /// 57 | /// - Parameters: 58 | /// - navigationController: Any UINavigationController 59 | /// - viewController: Any UIViewController 60 | /// - selectedImage: The selected image 61 | /// - unselectedImage: The unselected image 62 | /// - title: Tab bar title 63 | /// - Returns: UINavigationController with all 64 | func createViewController(nv navigationController: T, 65 | vc viewController: UIViewController, 66 | selectedImage: UIImage, 67 | unselectedImage: UIImage, 68 | title: String?) -> T { 69 | let vc = viewController 70 | vc.tabBarItem.selectedImage = selectedImage 71 | vc.tabBarItem.image = unselectedImage 72 | vc.title = title 73 | 74 | return T(rootViewController: vc) 75 | } 76 | 77 | /// making the tab bar with gradient image 78 | func updateImageWithGradient(colors: [CGColor]) { 79 | 80 | let navBarHeight = tabBar.frame.size.height 81 | let statusBarHeight = UIApplication.shared.statusBarFrame.height 82 | let heightAdjustment: CGFloat = 2 83 | let gradientHeight = navBarHeight + statusBarHeight + heightAdjustment 84 | 85 | let bgImage = imageWithGradient(colors: colors, 86 | size: CGSize(width: UIScreen.main.bounds.size.width, 87 | height: gradientHeight)) 88 | guard let image = bgImage else { return } 89 | tabBar.barTintColor = UIColor(patternImage: image) 90 | } 91 | 92 | /// Create an UIImage 93 | /// 94 | /// - Parameters: 95 | /// - colors: Array of gradient colors 96 | /// - size: CGSize 97 | /// - Returns: UIImage? 98 | private func imageWithGradient(colors: [CGColor], 99 | size: CGSize, locations: [NSNumber] = [0, 1], 100 | startPoint: CGPoint = CGPoint(x: 0.25, y: 0.5), 101 | endPoint: CGPoint = CGPoint(x: 0.75, y: 0.5), 102 | transform: CATransform3D = CATransform3DMakeAffineTransform(CGAffineTransform(a: 0, b: 1, c: -1, d: 0, tx: 1, ty: 0))) -> UIImage? { 103 | 104 | let gradientLayer = CAGradientLayer() 105 | gradientLayer.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) 106 | gradientLayer.colors = colors 107 | gradientLayer.locations = locations 108 | gradientLayer.bounds = view.bounds.insetBy(dx: -0.5 * size.width, dy: -0.5 * size.height) 109 | gradientLayer.startPoint = startPoint 110 | gradientLayer.endPoint = endPoint 111 | gradientLayer.position = self.view.center 112 | gradientLayer.transform = transform 113 | 114 | UIGraphicsBeginImageContext(gradientLayer.bounds.size) 115 | if let context = UIGraphicsGetCurrentContext() { 116 | gradientLayer.render(in: context) 117 | } 118 | let image = UIGraphicsGetImageFromCurrentImageContext() 119 | UIGraphicsEndImageContext() 120 | return image 121 | } 122 | } 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/UITableView+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 18/05/2019. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | public extension UITableView { 12 | 13 | /// Index path of last row in tableView. 14 | var indexPathForLastRow: IndexPath? { 15 | return indexPathForLastRow(inSection: lastSection) 16 | } 17 | 18 | /// Index of last section in tableView. 19 | var lastSection: Int { 20 | return numberOfSections > 0 ? numberOfSections - 1 : 0 21 | } 22 | } 23 | 24 | public extension UITableView { 25 | 26 | /// Remove TableFooterView. 27 | func removeTableFooterView() { 28 | tableFooterView = nil 29 | } 30 | 31 | /// Remove TableHeaderView. 32 | func removeTableHeaderView() { 33 | tableHeaderView = nil 34 | } 35 | 36 | /// Scroll to bottom of TableView. 37 | /// 38 | /// - Parameter animated: set true to animate scroll (default is true). 39 | func scrollToBottom(animated: Bool = true) { 40 | let bottomOffset = CGPoint(x: 0, y: contentSize.height - bounds.size.height) 41 | setContentOffset(bottomOffset, animated: animated) 42 | } 43 | 44 | /// Scroll to top of TableView. 45 | /// 46 | /// - Parameter animated: set true to animate scroll (default is true). 47 | func scrollToTop(animated: Bool = true) { 48 | setContentOffset(CGPoint.zero, animated: animated) 49 | } 50 | 51 | /// Reload data with a completion handler. 52 | /// 53 | /// - Parameter completion: completion handler to run after reloadData finishes. 54 | func reloadData(_ completion: @escaping () -> Void) { 55 | UIView.animate(withDuration: 0, animations: { 56 | self.reloadData() 57 | }, completion: { _ in 58 | completion() 59 | }) 60 | } 61 | 62 | /// Number of all rows in all sections of tableView. 63 | /// 64 | /// - Returns: The count of all rows in the tableView. 65 | func numberOfRows() -> Int { 66 | var section = 0 67 | var rowCount = 0 68 | while section < numberOfSections { 69 | rowCount += numberOfRows(inSection: section) 70 | section += 1 71 | } 72 | return rowCount 73 | } 74 | 75 | /// IndexPath for last row in section. 76 | /// 77 | /// - Parameter section: section to get last row in. 78 | /// - Returns: optional last indexPath for last row in section (if applicable). 79 | func indexPathForLastRow(inSection section: Int) -> IndexPath? { 80 | guard section >= 0 else { return nil } 81 | guard numberOfRows(inSection: section) > 0 else { 82 | return IndexPath(row: 0, section: section) 83 | } 84 | return IndexPath(row: numberOfRows(inSection: section) - 1, section: section) 85 | } 86 | 87 | /// Register UITableViewCell with .xib file using only its corresponding class. 88 | /// Assumes that the .xib filename and cell class has the same name. 89 | /// 90 | /// - Parameters: 91 | /// - name: UITableViewCell type. 92 | /// - bundleClass: Class in which the Bundle instance will be based on. 93 | func registerNibCell(nibWithCellClass name: T.Type, at bundleClass: AnyClass? = nil) { 94 | let identifier = String(describing: name) 95 | var bundle: Bundle? 96 | if let bundleName = bundleClass { 97 | bundle = Bundle(for: bundleName) 98 | } 99 | register(UINib(nibName: identifier, bundle: bundle), forCellReuseIdentifier: identifier) 100 | } 101 | 102 | /// Dequeue reusable UITableViewCell using class name 103 | /// 104 | /// - Parameter name: UITableViewCell type 105 | /// - Returns: UITableViewCell object with associated class name. 106 | func dequeueReusableCell(withClass name: T.Type) -> T { 107 | guard let cell = dequeueReusableCell(withIdentifier: String(describing: name)) as? T else { 108 | return T() 109 | } 110 | return cell 111 | } 112 | 113 | /// Dequeue reusable UITableViewCell using class name for indexPath 114 | /// 115 | /// - Parameters: 116 | /// - name: UITableViewCell type. 117 | /// - indexPath: location of cell in tableView. 118 | /// - Returns: UITableViewCell object with associated class name. 119 | func dequeueReusableCell(withClass name: T.Type, for indexPath: IndexPath) -> T { 120 | guard let cell = dequeueReusableCell(withIdentifier: String(describing: name), for: indexPath) as? T else { 121 | return T() 122 | } 123 | return cell 124 | } 125 | 126 | /// Check whether IndexPath is valid within the tableView 127 | /// 128 | /// - Parameter indexPath: An IndexPath to check 129 | /// - Returns: Boolean value for valid or invalid IndexPath 130 | func isValidIndexPath(_ indexPath: IndexPath) -> Bool { 131 | return indexPath.section < numberOfSections && indexPath.row < numberOfRows(inSection: indexPath.section) 132 | } 133 | 134 | /// Safely scroll to possibly invalid IndexPath 135 | /// 136 | /// - Parameters: 137 | /// - indexPath: Target IndexPath to scroll to 138 | /// - scrollPosition: Scroll position 139 | /// - animated: Whether to animate or not 140 | func safeScrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool) { 141 | guard indexPath.section < numberOfSections else { return } 142 | guard indexPath.row < numberOfRows(inSection: indexPath.section) else { return } 143 | scrollToRow(at: indexPath, at: scrollPosition, animated: animated) 144 | } 145 | 146 | /// Adding layers to tableView type grouped 147 | /// 148 | /// - Parameters: 149 | /// - tableView: UITableView 150 | /// - cell: Cell 151 | /// - indexPath: IndexPath 152 | func setRoundedCornersForGroupedCells(tableView: UITableView, cell: UITableViewCell, indexPath: IndexPath, color: UIColor) { 153 | if cell.responds(to: #selector(getter: UIView.tintColor)) { 154 | tableView.separatorStyle = .singleLine 155 | 156 | if cell.responds(to: #selector(getter: UIView.tintColor)) { 157 | let cornerRadius: CGFloat = 10.0 158 | if indexPath.row == 0 && indexPath.row == ( tableView.numberOfRows(inSection: indexPath.section) - 1) { 159 | // Only one cell in section 160 | cell.roundCorners(radius: cornerRadius) 161 | } else if indexPath.row == 0 { 162 | // First cell in section 163 | cell.roundCorners(corners: [.topLeft, .topRight], radius: cornerRadius) 164 | } else if indexPath.row == (tableView.numberOfRows(inSection: indexPath.section) - 1) { 165 | // Last cell in section 166 | cell.roundCorners(corners: [.bottomLeft, .bottomRight], radius: cornerRadius) 167 | } else { 168 | // Middle cells 169 | cell.roundCorners(radius: 0) 170 | } 171 | } 172 | } 173 | } 174 | } 175 | 176 | #endif 177 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/UITextField+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITextField+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 5/11/18. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | public enum UITextFieldMode { 12 | case ready 13 | case notReady 14 | } 15 | 16 | public extension UITextField { 17 | 18 | /// Checking if the textField is not empty to return notReady 19 | var mode: UITextFieldMode { 20 | guard let text = self.text else { return .notReady } 21 | return text.isNotEmpty == true ? (.ready) : (.notReady) 22 | } 23 | } 24 | 25 | public extension UITextField { 26 | 27 | /// Adding an image to the left of the textFeild. 28 | /// 29 | /// - Parameters: 30 | /// - image: image to use .. best to use PDF image. 31 | /// - color: image color 32 | func left(image: UIImage?, color: UIColor = .black, width: CGFloat = 20, height: CGFloat = 20) { 33 | if let image = image { 34 | leftViewMode = UITextField.ViewMode.always 35 | let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height)) 36 | imageView.contentMode = .scaleAspectFit 37 | imageView.image = image 38 | imageView.image = imageView.image?.withRenderingMode(.alwaysTemplate) 39 | imageView.tintColor = color 40 | leftView = imageView 41 | } else { 42 | leftViewMode = UITextField.ViewMode.never 43 | leftView = nil 44 | } 45 | } 46 | 47 | /// Adding an image to the right of the textFeild 48 | /// 49 | /// - Parameters: 50 | /// - image: image to use .. best to use PDF image. 51 | /// - color: image color 52 | func right(image: UIImage?, color: UIColor = .black, width: CGFloat = 20, height: CGFloat = 20) { 53 | if let image = image { 54 | rightViewMode = UITextField.ViewMode.always 55 | let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height)) 56 | imageView.contentMode = .scaleAspectFit 57 | imageView.image = image 58 | imageView.image = imageView.image?.withRenderingMode(.alwaysTemplate) 59 | imageView.tintColor = color 60 | rightView = imageView 61 | } else { 62 | rightViewMode = UITextField.ViewMode.never 63 | rightView = nil 64 | } 65 | } 66 | 67 | /// Set placeholder text color. 68 | /// 69 | /// - Parameter color: placeholder text color. 70 | func setPlaceHolderTextColor(_ color: UIColor) { 71 | self.attributedPlaceholder = NSAttributedString(string: self.placeholder ?? "", attributes: [NSAttributedString.Key.foregroundColor: color]) 72 | } 73 | 74 | /// Set placeholder text and its color 75 | func placeholderTextAndColor(text value: String, color: UIColor = .red) { 76 | self.attributedPlaceholder = NSAttributedString(string: value, attributes: [ NSAttributedString.Key.foregroundColor: color]) 77 | } 78 | 79 | /// Sets bottom border 80 | /// 81 | /// - Parameters: 82 | /// - color: Border color 83 | /// - borderHeight: Border height 84 | func setBottomBorder(withColor color: UIColor, borderHeight: CGFloat) { 85 | borderStyle = .none 86 | layer.backgroundColor = UIColor.white.cgColor 87 | 88 | layer.masksToBounds = false 89 | layer.shadowColor = color.cgColor 90 | layer.shadowOffset = CGSize(width: 0.0, height: borderHeight) 91 | layer.shadowOpacity = 1.0 92 | layer.shadowRadius = 0.0 93 | } 94 | } 95 | #endif 96 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/UITextView+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITextView+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 18/05/2019. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | public extension UITextView { 12 | 13 | /// Scroll to the bottom of text view 14 | func scrollToBottom() { 15 | let range = NSRange(location: (text as String).count - 1, length: 1) 16 | scrollRangeToVisible(range) 17 | } 18 | 19 | /// Scroll to the top of text view 20 | func scrollToTop() { 21 | let range = NSRange(location: 0, length: 1) 22 | scrollRangeToVisible(range) 23 | } 24 | } 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/UIView+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 5/11/18. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | /// Two cases replacing `isHidden` 12 | /// 13 | /// - visible: means `isHidden = false` 14 | /// - hidden: means `isHidden = true` 15 | public enum UIViewDisplayMode { 16 | case visible 17 | case hidden 18 | // Please do not add any extra cases 19 | } 20 | 21 | public extension UIView { 22 | 23 | /// Elegent way to show and hide any UIView insted of `someView.isHidden = true` or `!someView.isHidden = true` 24 | var display: UIViewDisplayMode { 25 | get { 26 | return self.isHidden ? .hidden : .visible 27 | } 28 | set { 29 | self.isHidden = newValue == .hidden ? true : false 30 | } 31 | } 32 | 33 | var cornerRadius: CGFloat { 34 | get { 35 | return self.layer.cornerRadius 36 | } 37 | set { 38 | self.layer.cornerRadius = newValue 39 | } 40 | } 41 | 42 | var borderWidth: CGFloat { 43 | get { 44 | return self.layer.borderWidth 45 | } 46 | set { 47 | self.layer.borderWidth = newValue 48 | } 49 | } 50 | 51 | var borderColor: CGColor? { 52 | get { 53 | return self.layer.borderColor 54 | } 55 | set { 56 | self.layer.borderColor = newValue 57 | } 58 | } 59 | } 60 | 61 | public extension UIView { 62 | 63 | /// Rotate the image for RTL or LTR direction 64 | func flip() { 65 | self.transform = CGAffineTransform(scaleX: -1, y: 1) 66 | } 67 | 68 | /// Adding array of views to the subView 69 | /// 70 | /// - Parameter views: UIView | UIButton | UIImageView and all other UIKit elements 71 | func addSubviews(_ views: [UIView]) { 72 | views.forEach { addSubview($0) } 73 | } 74 | 75 | /// Adds a vertical gradient layer with two **UIColors** to the **UIView**. 76 | /// 77 | /// - Parameters: 78 | /// - topColor: The top 79 | /// - bottomColor: The bottom 80 | func addVerticalGradientLayer(topColor: UIColor, bottomColor: UIColor) { 81 | let gradient = CAGradientLayer() 82 | gradient.frame = self.bounds 83 | gradient.colors = [topColor.cgColor, bottomColor.cgColor] 84 | gradient.locations = [0.0, 1.0] 85 | gradient.startPoint = CGPoint(x: 0, y: 0) 86 | gradient.endPoint = CGPoint(x: 0, y: 1) 87 | self.layer.insertSublayer(gradient, at: 0) 88 | } 89 | 90 | /// Adding round corners to a UIView | UIButton | UIImageView and all other UIKit elements 91 | /// 92 | /// - Parameters: 93 | /// - corners: .topLeft | .topRight | .bottomLeft | .bottomRight 94 | /// - radius: corner radius 95 | func roundCorners(corners: UIRectCorner = .allCorners, radius: CGFloat) { 96 | if #available(iOS 11.0, *) { 97 | layer.cornerRadius = radius 98 | guard !corners.contains(.allCorners) else { return } 99 | layer.maskedCorners = [] 100 | if corners.contains(.topLeft) { 101 | layer.maskedCorners.insert(.layerMaxXMinYCorner) 102 | } 103 | if corners.contains(.topRight) { 104 | layer.maskedCorners.insert(.layerMinXMinYCorner) 105 | } 106 | if corners.contains(.bottomLeft) { 107 | layer.maskedCorners.insert(.layerMinXMaxYCorner) 108 | } 109 | if corners.contains(.bottomRight) { 110 | layer.maskedCorners.insert(.layerMaxXMaxYCorner) 111 | } 112 | } else { 113 | let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) 114 | let mask = CAShapeLayer() 115 | mask.path = path.cgPath 116 | layer.mask = mask 117 | } 118 | } 119 | 120 | /// Helper method for addSubviewFromNib() to load the nib file into UIView subclass 121 | private func viewFromNibForClass() -> UIView { 122 | let bundle = Bundle(for: type(of: self)) 123 | let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle) 124 | let view = nib.instantiate(withOwner: self, options: nil)[0] as? UIView 125 | return view ?? UIView() 126 | } 127 | 128 | /// Adding the nib file with UIView class 129 | func addSubviewFromNib() { 130 | let view = viewFromNibForClass() 131 | view.frame = bounds 132 | // autolayout 133 | view.autoresizingMask = [.flexibleWidth, .flexibleHeight] 134 | addSubview(view) 135 | } 136 | 137 | /// Setting up array of labels 138 | /// 139 | /// - Parameters: 140 | /// - labels: array of labels 141 | /// - textColor: text color for all 142 | /// - font: font 143 | func setupLabels(_ labels: [UILabel], textColor: UIColor? = nil, font: UIFont? = nil) { 144 | labels.forEach { 145 | if let newColor = textColor { 146 | $0.textColor = newColor 147 | } 148 | if let newFont = font { 149 | $0.font = newFont 150 | } 151 | } 152 | } 153 | 154 | /// Adding localization to array of UILabels 155 | /// 156 | /// - Parameter dictionary: `[UILabel: String]` String will be the localization key. 157 | func addLocalization(_ dictionary: [UILabel: String]) { 158 | for (label, string) in dictionary { 159 | label.text = string.localized 160 | } 161 | } 162 | 163 | // ---------------------------- UIViewAnimation -------------------------------- // 164 | 165 | /// Multible of cases to animate any UIView 166 | enum UIViewAnimation { 167 | /// Will change the color and animate if the duration > 0 168 | case changeColor(to: UIColor, duration: TimeInterval) 169 | /// Will hide the view and reduce the alpha value to 0 with animation if the duration > 0 170 | case hideView(duruation: TimeInterval) 171 | /// Will show the view and increase the alpha value to 1 with animation if the duration > 0 172 | case showView(duruation: TimeInterval) 173 | } 174 | 175 | /// Implimntation for all cases in `UIViewAnimation` 176 | /// 177 | /// - Parameter animation: UIViewAnimation 178 | func animate(_ animation: UIViewAnimation) { 179 | switch animation { 180 | case .changeColor(let newColor, let duration): 181 | UIView.animate(withDuration: duration) { 182 | self.backgroundColor = newColor 183 | } 184 | case .hideView(let duruation): 185 | UIView.animate(withDuration: duruation, animations: { 186 | self.alpha = 0 187 | }) { (finshed) in 188 | if finshed { self.display = .hidden } 189 | } 190 | case .showView(let duruation): 191 | UIView.animate(withDuration: duruation, animations: { 192 | self.alpha = 1 193 | }) { (finshed) in 194 | if finshed { self.display = .visible } 195 | } 196 | } 197 | } 198 | 199 | func applyShadowWithRoundCorners(masksToBounds: Bool = false, 200 | shadowColor: UIColor = .black, 201 | cornerRadius: CGFloat = 0.0, 202 | shadowOpacity: Float = 0.0, 203 | shadowOffset: CGSize = CGSize(width: 0, height: 0), 204 | shadowRadius: CGFloat = 0.0) { 205 | 206 | layer.masksToBounds = masksToBounds 207 | layer.shadowColor = shadowColor.cgColor 208 | layer.shadowOpacity = shadowOpacity 209 | layer.shadowOffset = shadowOffset 210 | layer.shadowRadius = shadowRadius 211 | layer.cornerRadius = cornerRadius 212 | layer.shouldRasterize = false 213 | } 214 | 215 | /// Create an image from a UIView 216 | /// 217 | /// - Returns: optional image 218 | func takeScreenshot() -> UIImage? { 219 | UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, UIScreen.main.scale) 220 | drawHierarchy(in: self.bounds, afterScreenUpdates: true) 221 | let image = UIGraphicsGetImageFromCurrentImageContext() 222 | UIGraphicsEndImageContext() 223 | return image 224 | } 225 | 226 | /// Added a motion effect with the device movment 227 | /// 228 | /// - Parameter value: value to move, def: Int == 40 229 | func addMotionEffects(value: Int = 40) { 230 | 231 | // Add object movement with the device movment 232 | let horizontalEffect1 = UIInterpolatingMotionEffect( 233 | keyPath: "center.x", 234 | type: .tiltAlongHorizontalAxis) 235 | horizontalEffect1.minimumRelativeValue = -value 236 | horizontalEffect1.maximumRelativeValue = value 237 | 238 | let verticalEffect1 = UIInterpolatingMotionEffect( 239 | keyPath: "center.y", 240 | type: .tiltAlongVerticalAxis) 241 | verticalEffect1.minimumRelativeValue = -value 242 | verticalEffect1.maximumRelativeValue = value 243 | 244 | let effectGroup1 = UIMotionEffectGroup() 245 | effectGroup1.motionEffects = [horizontalEffect1, verticalEffect1] 246 | addMotionEffect(effectGroup1) 247 | } 248 | 249 | /// Adding fade transition 250 | /// 251 | /// - Parameter duration: CFTimeInterval 252 | func fadeTransition(_ duration: CFTimeInterval) { 253 | let animation = CATransition() 254 | animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) 255 | animation.type = CATransitionType.fade 256 | animation.duration = duration 257 | layer.add(animation, forKey: CATransitionType.fade.rawValue) 258 | } 259 | 260 | /// Set layer corners & borderWidth & borderColors in one line. Default values is `nil` 261 | func layer(corners: CGFloat? = nil, borderWidth: CGFloat? = nil, borderColors: UIColor? = nil) { 262 | if let corners = corners { 263 | layer.cornerRadius = corners 264 | } 265 | if let borderWidth = borderWidth { 266 | layer.borderWidth = borderWidth 267 | } 268 | if let borderColors = borderColors { 269 | layer.borderColor = borderColors.cgColor 270 | } 271 | } 272 | } 273 | 274 | #endif 275 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/UIViewAutoLayout+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewAutoLayout+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 18/05/2019. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | public extension UIView { 12 | 13 | /* ------------------------------- Auto layout -------------------------------- */ 14 | 15 | var safeTopAnchor: NSLayoutYAxisAnchor { 16 | if #available(iOS 11.0, *) { 17 | return safeAreaLayoutGuide.topAnchor 18 | } 19 | return topAnchor 20 | } 21 | 22 | var safeLeftAnchor: NSLayoutXAxisAnchor { 23 | if #available(iOS 11.0, *) { 24 | return safeAreaLayoutGuide.leftAnchor 25 | } 26 | return leftAnchor 27 | } 28 | 29 | var safeBottomAnchor: NSLayoutYAxisAnchor { 30 | if #available(iOS 11.0, *) { 31 | return safeAreaLayoutGuide.bottomAnchor 32 | } 33 | return bottomAnchor 34 | } 35 | 36 | var safeRightAnchor: NSLayoutXAxisAnchor { 37 | if #available(iOS 11.0, *) { 38 | return safeAreaLayoutGuide.rightAnchor 39 | } 40 | return rightAnchor 41 | } 42 | 43 | /// A layout anchor representing all the edges of the view’s frame. 44 | /// 45 | /// - Parameters: 46 | /// - top: top anchor of the view 47 | /// - left: left anchor of the view 48 | /// - bottom: bottom anchor of the view 49 | /// - right: right anchor of the view 50 | /// - topConstant: constant of the top anchor 51 | /// - leftConstant: constant of the left anchor 52 | /// - bottomConstant: constant of the bottom anchor 53 | /// - rightConstant: constant of the right anchor 54 | func anchor(top: NSLayoutYAxisAnchor? = nil, 55 | left: NSLayoutXAxisAnchor? = nil, 56 | bottom: NSLayoutYAxisAnchor? = nil, 57 | right: NSLayoutXAxisAnchor? = nil, 58 | topConstant: CGFloat = 0, 59 | leftConstant: CGFloat = 0, 60 | bottomConstant: CGFloat = 0, 61 | rightConstant: CGFloat = 0) { 62 | 63 | translatesAutoresizingMaskIntoConstraints = false 64 | 65 | if let top = top { 66 | topAnchor.constraint(equalTo: top, constant: topConstant).isActive = true 67 | } 68 | 69 | if let bottom = bottom { 70 | bottomAnchor.constraint(equalTo: bottom, constant: -bottomConstant).isActive = true 71 | } 72 | 73 | if let left = left { 74 | leftAnchor.constraint(equalTo: left, constant: leftConstant).isActive = true 75 | } 76 | 77 | if let right = right { 78 | rightAnchor.constraint(equalTo: right, constant: -rightConstant).isActive = true 79 | } 80 | 81 | } 82 | 83 | func anchorToEdges(view: UIView, padding: CGFloat = 0) { 84 | anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, 85 | topConstant: padding, leftConstant: padding, bottomConstant: padding, rightConstant: padding) 86 | } 87 | 88 | /// A layout anchor representing the dimensions of the view’s frame. 89 | /// 90 | /// - Parameters: 91 | /// - height: height of the view 92 | /// - width: width of the view 93 | /// - centerX: X center of the view 94 | /// - centerY: Y center of the view 95 | /// - centerXConstant: constant of X 96 | /// - centerYConstant: constant of Y 97 | func anchorWithDimensions(height: CGFloat? = nil, 98 | width: CGFloat? = nil, 99 | centerX: NSLayoutXAxisAnchor? = nil, 100 | centerY: NSLayoutYAxisAnchor? = nil, 101 | centerXConstant: CGFloat = 0, 102 | centerYConstant: CGFloat = 0) { 103 | 104 | translatesAutoresizingMaskIntoConstraints = false 105 | 106 | if let height = height { 107 | heightAnchor.constraint(equalToConstant: height).isActive = true 108 | } 109 | 110 | if let width = width { 111 | widthAnchor.constraint(equalToConstant: width).isActive = true 112 | } 113 | 114 | if let centerX = centerX { 115 | centerXAnchor.constraint(equalTo: centerX, constant: centerXConstant).isActive = true 116 | } 117 | 118 | if let centerY = centerY { 119 | centerYAnchor.constraint(equalTo: centerY, constant: -centerYConstant).isActive = true 120 | } 121 | } 122 | 123 | /// A layout anchor representing the dimensions with multiplier of the view’s frame. 124 | /// 125 | /// - Parameters: 126 | /// - height: height of the view you want to count on 127 | /// - width: width of the view you want to count on 128 | /// - heightMultiplier: height multiplier 129 | /// - widthMultiplier: width multiplier 130 | /// - centerX: X center of the view 131 | /// - centerY: Y center of the view 132 | /// - centerXConstant: constant of X 133 | /// - centerYConstant: constant of Y 134 | func anchorWithMultiplier(height: NSLayoutDimension? = nil, 135 | width: NSLayoutDimension? = nil, 136 | heightMultiplier: CGFloat = 1, 137 | widthMultiplier: CGFloat = 1, 138 | centerX: NSLayoutXAxisAnchor? = nil, 139 | centerY: NSLayoutYAxisAnchor? = nil, 140 | centerXConstant: CGFloat = 0, 141 | centerYConstant: CGFloat = 0) { 142 | 143 | translatesAutoresizingMaskIntoConstraints = false 144 | 145 | if let height = height { 146 | heightAnchor.constraint(equalTo: height, multiplier: heightMultiplier).isActive = true 147 | } 148 | 149 | if let width = width { 150 | widthAnchor.constraint(equalTo: width, multiplier: widthMultiplier).isActive = true 151 | } 152 | 153 | if let centerX = centerX { 154 | centerXAnchor.constraint(equalTo: centerX, constant: centerXConstant).isActive = true 155 | } 156 | 157 | if let centerY = centerY { 158 | centerYAnchor.constraint(equalTo: centerY, constant: -centerYConstant).isActive = true 159 | } 160 | } 161 | 162 | /// Filling the view to it's superview 163 | func fillSuperview() { 164 | guard let superview = self.superview else { return } 165 | translatesAutoresizingMaskIntoConstraints = superview.translatesAutoresizingMaskIntoConstraints 166 | if translatesAutoresizingMaskIntoConstraints { 167 | autoresizingMask = [.flexibleWidth, .flexibleHeight] 168 | frame = superview.bounds 169 | } else { 170 | topAnchor.constraint(equalTo: superview.topAnchor).isActive = true 171 | bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true 172 | leftAnchor.constraint(equalTo: superview.leftAnchor).isActive = true 173 | rightAnchor.constraint(equalTo: superview.rightAnchor).isActive = true 174 | } 175 | } 176 | } 177 | 178 | #endif 179 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/UIViewController+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 8/26/18. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | public extension UIViewController { 12 | var backgroundColor: UIColor? { 13 | get { 14 | return view.backgroundColor 15 | } 16 | set { 17 | view.backgroundColor = newValue 18 | } 19 | } 20 | } 21 | 22 | public extension UIViewController { 23 | 24 | /// changing the back bar button title and color for subViewControllers 25 | /// 26 | /// - Parameter stringToUse: back bar button title 27 | func setBackButtonTitle(_ stringToUse: String){ 28 | let titleToSet = stringToUse 29 | let bacButton = UIBarButtonItem(title: titleToSet, style: .plain, target: nil, action: nil) 30 | navigationItem.backBarButtonItem = bacButton 31 | } 32 | 33 | /// Show any viewController as a root 34 | /// 35 | /// - Parameters: 36 | /// - flag: animation flag 37 | /// - completion: completion 38 | func show(animated flag: Bool, completion: (() -> Void)? = nil) { 39 | let window = UIWindow(frame: UIScreen.main.bounds) 40 | window.rootViewController = UIViewController() 41 | window.windowLevel = UIWindow.Level.alert 42 | window.makeKeyAndVisible() 43 | window.tintColor = .red 44 | window.rootViewController?.present(self, animated: flag, completion: completion) 45 | } 46 | 47 | /// Setting the navigation title and tab bar title 48 | /// 49 | /// - Parameters: 50 | /// - navigationTitle: Navigation title 51 | /// - tabBarTitle: TabBar title 52 | func setTitles(navigationTitle: String? = nil, tabBarTitle: String? = nil) { 53 | // Order is important here! 54 | if let tabBarTitle = tabBarTitle { 55 | title = tabBarTitle 56 | } 57 | if let navigationTitle = navigationTitle { 58 | navigationItem.title = navigationTitle 59 | } 60 | } 61 | 62 | /// Setting the backBarButtonItem for the next screen to "" to hide it. 63 | func hideBackButtonTitle() { 64 | navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) 65 | } 66 | 67 | /// Removing the shadow image from the navigationBar 68 | func hideShadowImageFromNavigation() { 69 | navigationController?.navigationBar.shadowImage = UIImage() 70 | } 71 | 72 | /// Adding a tap gesture recognizer to hide the keyboard 73 | func addKeyboardHidingGesture() { 74 | let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) 75 | tap.cancelsTouchesInView = false 76 | view.addGestureRecognizer(tap) 77 | } 78 | 79 | /// selector method for addKeyboardHidingGesture() 80 | @objc func dismissKeyboard() { 81 | view.endEditing(true) 82 | } 83 | 84 | /// Instantiate a UIViewController using its storyboard and class name 85 | /// 86 | /// - Parameters: 87 | /// - name: UIViewController type 88 | /// - storyboard: UIStoryboard 89 | /// - Returns: The view controller corresponding to specified class name 90 | func vcFromStoryboard(withClass name: T.Type, from storyboard: UIStoryboard) -> T { 91 | return storyboard.instantiateVC(withClass: name.self) 92 | } 93 | 94 | /// Presents a view controller modally. 95 | func presentThis(_ vc: UIViewController, animated: Bool = true, completion: (() -> Void)? = nil) { 96 | present(vc, animated: animated, completion: completion) 97 | } 98 | 99 | /// Pushes a view controller onto the receiver’s stack and updates the display. 100 | func pushThis(_ vc: UIViewController, animated: Bool = true) { 101 | navigationController?.pushViewController(vc, animated: animated) 102 | } 103 | 104 | /// Dismisses the view controller that was presented modally by the view controller. 105 | func dismissThis(animated: Bool = true, completion: (() -> Void)? = nil) { 106 | dismiss(animated: animated, completion: completion) 107 | } 108 | 109 | /// Pops the top view controller from the navigation stack and updates the display. 110 | func pop(animated: Bool = true) { 111 | navigationController?.popViewController(animated: animated) 112 | } 113 | 114 | /// Pops all the view controllers on the stack except the root view controller and updates the display. 115 | func popToRoot(animated: Bool = true) { 116 | navigationController?.popToRootViewController(animated: animated) 117 | } 118 | 119 | /// Animating dictionary of constrints 120 | /// 121 | /// - Parameters: 122 | /// - constraints: NSLayoutConstraint and CGFloat 123 | /// - duration: TimeInterval for the animation, default is 0.3 124 | func animateConstrints(constraints: [NSLayoutConstraint: CGFloat], duration: TimeInterval = 0.3) { 125 | UIView.animate(withDuration: duration) { 126 | for (constraint, value) in constraints { 127 | constraint.constant = value 128 | } 129 | self.view.layoutIfNeeded() 130 | } 131 | } 132 | } 133 | 134 | #endif 135 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/UIViewController+LocalAuthentication+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+LocalAuthentication+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 23/08/2019. 6 | // 7 | 8 | #if !os(macOS) 9 | import LocalAuthentication 10 | import UIKit 11 | 12 | public extension UIViewController { 13 | 14 | func canUseFaceTouchID(Complition: @escaping (Bool) -> Void) { 15 | let context = LAContext() 16 | var error: NSError? 17 | 18 | if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { 19 | let reason = "Sign in using your FaceID" 20 | context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, _ in 21 | 22 | DispatchQueue.main.async { 23 | if success { 24 | Complition(true) 25 | } else { 26 | // error 27 | Complition(false) 28 | } 29 | } 30 | } 31 | } else { 32 | // no biometry 33 | Complition(false) 34 | } 35 | } 36 | } 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Extensions/URL+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+Extension.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 23/08/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | // CoreData releated 11 | public extension URL { 12 | 13 | /// Creates a store URL for the given app group and database pointing to the sqlite database 14 | /// 15 | /// ``` 16 | /// import CoreData 17 | /// 18 | /// struct PersistenceController { 19 | /// static let shared = PersistenceController() 20 | /// 21 | /// let container: NSPersistentCloudKitContainer 22 | /// 23 | /// init() { 24 | /// let storeURL = URL.storeURL(for: "group.com.example.MyApp", databaseName: "MyApp") 25 | /// let storeDescription = NSPersistentStoreDescription(url: storeURL) 26 | /// 27 | /// container = NSPersistentCloudKitContainer(name: "MyApp") 28 | /// container.persistentStoreDescriptions = [storeDescription] 29 | /// container.loadPersistentStores(completionHandler: { (storeDescription, error) in 30 | /// if let error = error as NSError? { 31 | /// fatalError("Unresolved error \(error), \(error.userInfo)") 32 | /// } 33 | /// }) 34 | /// container.viewContext.automaticallyMergesChangesFromParent = true 35 | /// } 36 | /// } 37 | /// ``` 38 | /// 39 | /// - Parameters: 40 | /// - appGroup: Your app group id like > `"group.com.example.MyApp"` 41 | /// - databaseName: Your app database name 42 | /// 43 | /// - Returns: URL for the given app group 44 | static func storeURL(for appGroup: String, databaseName: String) -> URL { 45 | guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else { 46 | fatalError("Shared file container could not be created.") 47 | } 48 | 49 | return fileContainer.appendingPathComponent("\(databaseName).sqlite") 50 | } 51 | } 52 | 53 | public extension URL { 54 | 55 | /// Converting regular url into query parameters 56 | var queryParameters: [String: String]? { 57 | guard 58 | let components = URLComponents(url: self, resolvingAgainstBaseURL: true), 59 | let queryItems = components.queryItems else { return nil } 60 | return queryItems.reduce(into: [String: String]()) { (result, item) in 61 | result[item.name] = item.value 62 | } 63 | } 64 | } 65 | 66 | public extension Collection where Element == URLQueryItem { 67 | /// Directly access the values by going over the query item key 68 | subscript(_ name: String) -> String? { 69 | first(where: { $0.name == name })?.value 70 | } 71 | } 72 | 73 | /// Convert a dictionary into an array of query items 74 | public extension Array where Element == URLQueryItem { 75 | init(_ dictionary: [String: T]) { 76 | self = dictionary.map({ (key, value) -> Element in 77 | URLQueryItem(name: key, value: String(value)) 78 | }) 79 | } 80 | } 81 | 82 | public extension URL { 83 | 84 | /// Initializing a URL from a `StaticString` which is known at compile time. 85 | /// 86 | /// - Disclaimer: 87 | /// This uses a StaticString 88 | /// 89 | /// - Parameter string: URL 90 | init(_ string: StaticString) { 91 | self.init(string: "\(string)")! 92 | } 93 | 94 | subscript(_ name: String) -> String? { 95 | let urlComponents = URLComponents(string: self.absoluteString) 96 | let queryItems = urlComponents?.queryItems 97 | return queryItems?[name] 98 | } 99 | 100 | func addingURLQueryParameters(_ params: [String: String]) -> URL { 101 | guard var urlComponents = URLComponents(string: self.absoluteString) else { return self } 102 | urlComponents.queryItems = .init(params) 103 | return urlComponents.url ?? self 104 | } 105 | } 106 | 107 | public extension String { 108 | subscript(_ name: String) -> String? { 109 | guard let url = URL(string: self) else { return nil } 110 | let urlComponents = URLComponents(string: url.absoluteString) 111 | let queryItems = urlComponents?.queryItems 112 | return queryItems?[name] 113 | } 114 | 115 | func addingURLQueryParameters(_ params: [String: String]) -> String { 116 | guard var urlComponents = URLComponents(string: self) else { return self } 117 | urlComponents.queryItems = .init(params) 118 | return urlComponents.string ?? self 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/Helper4Swift.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public struct H4S { } 5 | 6 | public extension H4S { // Alerts 7 | 8 | /// Typealias for alertButtonAction 9 | typealias AlertButtonAction = (() -> Void)? 10 | 11 | /// Enum for alert type 12 | /// 13 | /// - success: success alert 14 | /// - failure: failure alert 15 | /// - normal: normal alert 16 | enum AlertType { 17 | case success, failure, normal 18 | } 19 | 20 | /// Show an alert with diffrent configations like time to dismiss this alert and button completion and is it .success or .normal 21 | /// 22 | /// - Parameters: 23 | /// - vc: UIViewController to show this alert on 24 | /// - alertType: type of the alert like .success to show green alert 25 | /// - title: alert title 26 | /// - body: alert message 27 | /// - dismissAfter: time to dismiss the alert 28 | /// - buttonTitle: button title 29 | /// - completion: button action completion 30 | static func showAlert(vc: UIViewController? = UIApplication.topViewController(), 31 | _ alertType: AlertType, 32 | title: String? = nil, 33 | body: String? = nil, 34 | dismissAfter: TimeInterval? = nil, 35 | buttonTitle: String? = nil, 36 | completion: AlertButtonAction = nil) { 37 | 38 | let alert = UIAlertController(title: title, message: body, preferredStyle: .alert) 39 | if let buttonTitle = buttonTitle { 40 | let ok = UIAlertAction(title: buttonTitle, style: .default) { (_) in 41 | completion?() 42 | } 43 | alert.addAction(ok) 44 | } 45 | // Accessing alert view backgroundColor : 46 | switch alertType { 47 | case .success: 48 | alert.view.subviews.first?.subviews.first?.subviews.first?.backgroundColor = #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1) 49 | case .failure: 50 | alert.view.subviews.first?.subviews.first?.subviews.first?.backgroundColor = #colorLiteral(red: 0.7967291667, green: 0.1695612325, blue: 0.08168510124, alpha: 1) 51 | case .normal: 52 | break 53 | } 54 | // Accessing buttons tintcolor : 55 | alert.view.tintColor = .black 56 | 57 | // presinting the alert controller ... 58 | vc?.present(alert, animated: true, completion: nil) 59 | 60 | if let timeToDismiss = dismissAfter { 61 | DispatchQueue.main.asyncAfter(deadline: .now() + timeToDismiss) { 62 | vc?.dismiss(animated: true, completion: nil) 63 | } 64 | } 65 | } 66 | 67 | /* -------------------------------- Basic Alerts ---------------------------------------- */ 68 | 69 | /// show a basic alert with title and message only 70 | /// 71 | /// - Parameters: 72 | /// - vc: UIViewController to show this alert on 73 | /// - title: alert title 74 | /// - message: alert message 75 | /// - buttonTitle: alert button text 76 | static func showBasicAlert(vc: UIViewController, title: String?, message: String?, buttonTitle: String) { 77 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 78 | alert.addAction(UIAlertAction(title: buttonTitle, style: .default, handler: nil)) 79 | vc.present(alert, animated: true) 80 | } 81 | 82 | /// show an alert with title, message and one button with action 83 | /// 84 | /// - Parameters: 85 | /// - vc: UIViewController to show this alert on 86 | /// - title: title to set 87 | /// - message: message to set 88 | /// - buttonTitle: button title to set 89 | /// - buttonHandler: completion handler for the button 90 | static func showOneActionAlert(vc: UIViewController, title: String?, message: String?, buttonTitle: String, buttonHandler: AlertButtonAction = nil) { 91 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 92 | alert.addAction(UIAlertAction(title: buttonTitle, style: .default) { _ in 93 | buttonHandler?() 94 | }) 95 | vc.present(alert, animated: true, completion: nil) 96 | } 97 | 98 | /// show an alert with title, message and two buttons 99 | /// 100 | /// - Parameters: 101 | /// - title: title to set 102 | /// - message: message to set 103 | /// - btn1Title: button 1 title to set 104 | /// - btn2Title: button 2 title to set 105 | /// - btn1Handler: completion handler for the first button 106 | static func showTwoActionsAlert(vc: UIViewController? = UIApplication.topViewController(), 107 | title: String?, 108 | message: String?, 109 | btn1Title: String, 110 | btn2Title: String, 111 | btn1Handler: AlertButtonAction = nil) { 112 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 113 | let action1 = UIAlertAction(title: btn1Title, style: .default) { _ in 114 | btn1Handler?() 115 | } 116 | let action2 = UIAlertAction(title: btn2Title, style: .default) 117 | alert.addAction(action1) 118 | alert.addAction(action2) 119 | vc?.present(alert, animated: true, completion: nil) 120 | } 121 | } 122 | 123 | public extension H4S { // Haptics 124 | 125 | /// List of feedback 126 | enum ImpactType { 127 | /// UIImpactFeedbackGenerator(style: .light) 128 | case light 129 | /// UIImpactFeedbackGenerator(style: .medium) 130 | case medium 131 | /// UIImpactFeedbackGenerator(style: .heavy) 132 | case heavy 133 | /// notificationOccurred(.success) 134 | case success 135 | /// notificationOccurred(.warning) 136 | case warning 137 | /// notificationOccurred(.error) 138 | case error 139 | } 140 | 141 | /// A concrete UIFeedbackGenerator subclass that creates haptics to simulate physical impacts. 142 | /// 143 | /// - Parameter type: A collision between small, light,.., user interface elements 144 | static func feedbackGenerator(type: ImpactType) { 145 | if #available(iOS 10.0, *) { 146 | switch type { 147 | case .light: 148 | let lightGenerator = UIImpactFeedbackGenerator(style: .light) 149 | lightGenerator.prepare() 150 | lightGenerator.impactOccurred() 151 | 152 | case .medium: 153 | let mediumGenerator = UIImpactFeedbackGenerator(style: .medium) 154 | mediumGenerator.prepare() 155 | mediumGenerator.impactOccurred() 156 | 157 | case .heavy: 158 | let heavyGenerator = UIImpactFeedbackGenerator(style: .heavy) 159 | heavyGenerator.prepare() 160 | heavyGenerator.impactOccurred() 161 | 162 | case .success: 163 | let generator = UINotificationFeedbackGenerator() 164 | generator.prepare() 165 | generator.notificationOccurred(.success) 166 | 167 | case .warning: 168 | let generator = UINotificationFeedbackGenerator() 169 | generator.prepare() 170 | generator.notificationOccurred(.warning) 171 | 172 | case .error: 173 | let generator = UINotificationFeedbackGenerator() 174 | generator.prepare() 175 | generator.notificationOccurred(.error) 176 | } 177 | } 178 | } 179 | } 180 | #endif 181 | -------------------------------------------------------------------------------- /Sources/Helper4Swift/SubClasses/UIViewFromNib.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewFromNib.swift 3 | // Helper4Swift 4 | // 5 | // Created by Abdullah Alhaider on 23/08/2019. 6 | // 7 | 8 | #if canImport(UIKit) 9 | import UIKit 10 | 11 | public class UIViewFromNib: UIView { 12 | 13 | override init(frame: CGRect) { 14 | super.init(frame: frame) 15 | setup() 16 | } 17 | 18 | required init?(coder aDecoder: NSCoder) { 19 | super.init(coder: aDecoder) 20 | setup() 21 | } 22 | 23 | private func setup() { 24 | addSubviewFromNib() 25 | setupViews() 26 | } 27 | 28 | /// Add your custom code here, no need to call super. 29 | public func setupViews() { } 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /Tests/Helper4SwiftTests/Helper4SwiftTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Helper4Swift 3 | 4 | final class Helper4SwiftTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | // XCTAssertEqual(Helper4Swift().text, "Hello, World!") 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Tests/Helper4SwiftTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(Helper4SwiftTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import Helper4SwiftTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += Helper4SwiftTests.allTests() 7 | XCTMain(tests) 8 | --------------------------------------------------------------------------------