├── .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 | [](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 |
--------------------------------------------------------------------------------