From 3f95a074db3067bdcddbfb4c144d7e6f96a0cc38 Mon Sep 17 00:00:00 2001 From: Kasper Tvede Date: Fri, 31 Mar 2023 07:53:24 +0200 Subject: [PATCH 1/6] added a few new extensions with tests fixed test relying on x86 simulator. minor cleaning as well. IDEA (appcode) should now be in git ignore. --- .gitignore | 1 + .../Collection/CollectionExtension.swift | 15 ++++----- .../UIView/UIViewExtension.swift | 33 ++++++++++--------- .../CollectionExtensionTests.swift | 31 ++++++++--------- .../UIDeviceExtensionTests.swift | 22 ++----------- .../UIViewExtensionTests.swift | 26 ++++++++++----- 6 files changed, 58 insertions(+), 70 deletions(-) diff --git a/.gitignore b/.gitignore index 28f8d57..c053b87 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,4 @@ TriforkSwiftExtensions/.DS_Store /.bundle/dependencies /.bundle/.DS_Store +TriforkSwiftExtensions/.idea diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions/Collection/CollectionExtension.swift b/TriforkSwiftExtensions/TriforkSwiftExtensions/Collection/CollectionExtension.swift index 95b06cb..849c12e 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensions/Collection/CollectionExtension.swift +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions/Collection/CollectionExtension.swift @@ -1,18 +1,15 @@ -// -// CollectionExtension.swift -// TriforkSwiftExtensions -// -// Created by Thomas Kalhøj Clemensen on 23/08/2017. -// Copyright © 2017 Trifork A/S. All rights reserved. -// - import Foundation public extension Collection { /// Returns the element at the specified index iff it is within bounds, otherwise nil. subscript (safe index: Index) -> Iterator.Element? { - return self.indices.contains(index) ? self[index] : nil + indices.contains(index) ? self[index] : nil + } + + /// Tells if this collection is NOT empty (contains element(s)) + var isNotEmpty: Bool { + !isEmpty } } diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions/UIView/UIViewExtension.swift b/TriforkSwiftExtensions/TriforkSwiftExtensions/UIView/UIViewExtension.swift index 152440f..4d1ce07 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensions/UIView/UIViewExtension.swift +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions/UIView/UIViewExtension.swift @@ -1,11 +1,3 @@ -// -// UIViewExtension.swift -// TriforkSwiftExtensions -// -// Created by Thomas Kalhøj Clemensen on 29/08/2017. -// Copyright © 2017 Trifork A/S. All rights reserved. -// - import UIKit public extension UIView { @@ -20,25 +12,25 @@ public extension UIView { /// /// This will also turn a rectangled view into a circle if it is a square. func roundHorizontalEdges() { - self.layer.cornerRadius = self.bounds.height / 2.0 + layer.cornerRadius = self.bounds.height / 2.0 } /// Rounds the top and bottom sides of the receiver. /// /// This will also turn a rectangled view into a circle if it is a square. func roundVerticalEdges() { - self.layer.cornerRadius = self.bounds.width / 2.0 + layer.cornerRadius = self.bounds.width / 2.0 } /// Gets the current first responder of the subviews. func currentFirstResponder() -> UIView? { var firstResponder: UIView? = nil - if self.isFirstResponder { + if isFirstResponder { firstResponder = self } else { - for view: UIView in self.subviews { + for view: UIView in subviews { firstResponder = view.currentFirstResponder() if firstResponder != nil { @@ -73,7 +65,7 @@ public extension UIView { break; } - self.layer.mask = mask + layer.mask = mask } /// Performs shake animation on view. @@ -90,7 +82,7 @@ public extension UIView { -shakeDistance/4.0, shakeDistance/4.0, 0.0 ] - self.layer.add(animation, forKey: animationKey) + layer.add(animation, forKey: animationKey) } @@ -107,7 +99,7 @@ public extension UIView { /// Finds the first responder in the receiver and its subviews. func findFirstResponder() -> UIView? { - return findFirstResponder(inView: self) + findFirstResponder(inView: self) } /// Adds `view` as subview to `self` and sets constraints for all edges with specified inset. @@ -139,4 +131,15 @@ public extension UIView { return firstResponder } + + + /// Makes this view hidden if it was visible before. + func hide() { + isHidden = true + } + + /// Makes this view visible if it was hidden before. + func show() { + isHidden = false + } } diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/CollectionExtensionTests.swift b/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/CollectionExtensionTests.swift index 8922fa5..520a6e5 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/CollectionExtensionTests.swift +++ b/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/CollectionExtensionTests.swift @@ -1,25 +1,8 @@ -// -// CollectionExtensionTests.swift -// TriforkSwiftExtensions -// -// Created by Thomas Kalhøj Clemensen on 23/08/2017. -// Copyright © 2017 Trifork A/S. All rights reserved. -// - import XCTest @testable import TriforkSwiftExtensions class CollectionExtensionTests: XCTestCase { - - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } + func testSafeIndex() { let array: [Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] @@ -32,4 +15,16 @@ class CollectionExtensionTests: XCTestCase { XCTAssert(array[safe: 10] == nil) XCTAssert(array[safe: 100000] == nil) } + + func testIsNotEmpty() { + let empty: [String] = [] + XCTAssert(empty.isNotEmpty == false, "Should not be \"not empty\" when its empty") + + let single = [""] + XCTAssert(single.isNotEmpty, "have content, so should not be empty") + + let muliple = [42, 42, 42] + XCTAssert(muliple.isNotEmpty, "have content, so should not be empty") + } + } diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/UIDeviceExtensionTests.swift b/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/UIDeviceExtensionTests.swift index fad95e9..f23503f 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/UIDeviceExtensionTests.swift +++ b/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/UIDeviceExtensionTests.swift @@ -1,28 +1,10 @@ -// -// UIDeviceExtensionTests.swift -// TriforkSwiftExtensions -// -// Created by Thomas Kalhøj Clemensen on 29/08/2017. -// Copyright © 2017 Trifork A/S. All rights reserved. -// - import XCTest @testable import TriforkSwiftExtensions class UIDeviceExtensionTests: XCTestCase { - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - func testModelName() { - XCTAssertEqual(UIDevice.current.modelName, "x86_64") //Simulator + let validSimulatorNames: [String] = ["x84_64", "arm64"] + XCTAssert(validSimulatorNames.contains(UIDevice.current.modelName)) } - } diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/UIViewExtensionTests.swift b/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/UIViewExtensionTests.swift index 6d32db3..8949cf0 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/UIViewExtensionTests.swift +++ b/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/UIViewExtensionTests.swift @@ -1,11 +1,3 @@ -// -// UIViewExtensionTests.swift -// TriforkSwiftExtensions -// -// Created by Thomas Kalhøj Clemensen on 29/08/2017. -// Copyright © 2017 Trifork A/S. All rights reserved. -// - import XCTest @testable import TriforkSwiftExtensions @@ -57,4 +49,22 @@ class UIViewExtensionTests: XCTestCase { XCTAssert(superview.bounds.equalTo(view.frame)) XCTAssert(superview.bounds.insetBy(dx: 50, dy: 50).equalTo(view2.frame)) } + + func testHide() { + let view = UIView() + view.isHidden = false + view.hide() + XCTAssert(view.isHidden == true, "view should be hidden when calling hide on a visible view") + view.hide() + XCTAssert(view.isHidden == true, "nothing should happen second time you call hide") + } + + func testShow() { + let view = UIView() + view.isHidden = true + view.show() + XCTAssert(view.isHidden == false, "view should be visible(not hidden) when calling show on an invisible view") + view.show() + XCTAssert(view.isHidden == false, "nothing should happen second time you call show") + } } From c5fe12a3934a777ab6574b5b8cb4cb6f3640f8b8 Mon Sep 17 00:00:00 2001 From: Kasper Tvede Date: Fri, 31 Mar 2023 08:15:22 +0200 Subject: [PATCH 2/6] more extensions --- .../project.pbxproj | 56 ++++++++++ .../Date/DateExtension.swift | 53 ++++++--- .../Dictionary/Dictionary.swift | 12 ++ .../Error/ErrorExtension.swift | 39 ++++--- .../String/StringExtension.swift | 103 ++++++++++-------- .../UIControl/Event/UIControlEvent.swift | 12 ++ .../UIFont/UIFont.swift | 12 ++ .../UITextField/UITextField.swift | 15 +++ .../UIView/UIViewExtension.swift | 13 +++ 9 files changed, 242 insertions(+), 73 deletions(-) create mode 100644 TriforkSwiftExtensions/TriforkSwiftExtensions/Dictionary/Dictionary.swift create mode 100644 TriforkSwiftExtensions/TriforkSwiftExtensions/UIControl/Event/UIControlEvent.swift create mode 100644 TriforkSwiftExtensions/TriforkSwiftExtensions/UIFont/UIFont.swift create mode 100644 TriforkSwiftExtensions/TriforkSwiftExtensions/UITextField/UITextField.swift diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj b/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj index 80f0549..acc7497 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 1A02D4EEEB24C7A1D8AB6327 /* UITextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02DF1C13CC7B2CF83E81B4 /* UITextField.swift */; }; + 1A02D7CC5B56C4F33C7D5ED5 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02DEA45C9ABA4960CEB69E /* Dictionary.swift */; }; + 1A02DC5570DD8FA92F06126C /* UIControlEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02D9A55662C7C993CF01A2 /* UIControlEvent.swift */; }; + 1A02DD8BA539DE7AE9A78D4F /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02D6CEB899DE021E506AC6 /* UIFont.swift */; }; C349B41C21639B2A007A4D37 /* CGRectExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C349B41B21639B2A007A4D37 /* CGRectExtension.swift */; }; C349B41E21639B5C007A4D37 /* CGRectExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C349B41D21639B5C007A4D37 /* CGRectExtensionTests.swift */; }; C35BD0D22272EB8E00B24F3B /* UITextViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35BD0D12272EB8E00B24F3B /* UITextViewExtensions.swift */; }; @@ -93,6 +97,10 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 1A02D6CEB899DE021E506AC6 /* UIFont.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = ""; }; + 1A02D9A55662C7C993CF01A2 /* UIControlEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIControlEvent.swift; sourceTree = ""; }; + 1A02DEA45C9ABA4960CEB69E /* Dictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = ""; }; + 1A02DF1C13CC7B2CF83E81B4 /* UITextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextField.swift; sourceTree = ""; }; C349B41B21639B2A007A4D37 /* CGRectExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGRectExtension.swift; sourceTree = ""; }; C349B41D21639B5C007A4D37 /* CGRectExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGRectExtensionTests.swift; sourceTree = ""; }; C35BD0D12272EB8E00B24F3B /* UITextViewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextViewExtensions.swift; sourceTree = ""; }; @@ -192,6 +200,46 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1A02D0BE082706696DFBE88A /* Dictionary */ = { + isa = PBXGroup; + children = ( + 1A02DEA45C9ABA4960CEB69E /* Dictionary.swift */, + ); + path = Dictionary; + sourceTree = ""; + }; + 1A02D1C835C6867C735D05DE /* UITextField */ = { + isa = PBXGroup; + children = ( + 1A02DF1C13CC7B2CF83E81B4 /* UITextField.swift */, + ); + path = UITextField; + sourceTree = ""; + }; + 1A02D2825AEC120C99288629 /* UIControl */ = { + isa = PBXGroup; + children = ( + 1A02DE048E6DCFD2782164D9 /* Event */, + ); + path = UIControl; + sourceTree = ""; + }; + 1A02DE048E6DCFD2782164D9 /* Event */ = { + isa = PBXGroup; + children = ( + 1A02D9A55662C7C993CF01A2 /* UIControlEvent.swift */, + ); + path = Event; + sourceTree = ""; + }; + 1A02DEAFD393A617ABCCEC22 /* UIFont */ = { + isa = PBXGroup; + children = ( + 1A02D6CEB899DE021E506AC6 /* UIFont.swift */, + ); + path = UIFont; + sourceTree = ""; + }; C349B41A21639B13007A4D37 /* CGRect */ = { isa = PBXGroup; children = ( @@ -519,6 +567,10 @@ E922693C1F5547C400DAEFAE /* UIView */, E922692C1F545B4C00DAEFAE /* UIViewController */, E92269241F5450F900DAEFAE /* URL */, + 1A02D0BE082706696DFBE88A /* Dictionary */, + 1A02DEAFD393A617ABCCEC22 /* UIFont */, + 1A02D1C835C6867C735D05DE /* UITextField */, + 1A02D2825AEC120C99288629 /* UIControl */, ); path = TriforkSwiftExtensions; sourceTree = ""; @@ -749,6 +801,10 @@ E946D3CC20FDE133002FFDFF /* UIEdgeInsetsExtension.swift in Sources */, E9DF346C1F4DA24800BB36BB /* NSMutableAttributedStringExtension.swift in Sources */, C37010BF221AF6370071CBF4 /* StringProtocolExtension.swift in Sources */, + 1A02D7CC5B56C4F33C7D5ED5 /* Dictionary.swift in Sources */, + 1A02DD8BA539DE7AE9A78D4F /* UIFont.swift in Sources */, + 1A02D4EEEB24C7A1D8AB6327 /* UITextField.swift in Sources */, + 1A02DC5570DD8FA92F06126C /* UIControlEvent.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions/Date/DateExtension.swift b/TriforkSwiftExtensions/TriforkSwiftExtensions/Date/DateExtension.swift index 34765f3..c8bfed8 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensions/Date/DateExtension.swift +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions/Date/DateExtension.swift @@ -9,7 +9,7 @@ import Foundation public extension Date { - + private struct Formatter { static let iso8601WithMs: DateFormatter = { let formatter = DateFormatter() @@ -19,7 +19,7 @@ public extension Date { formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" return formatter }() - + static let iso8601WithoutMs: DateFormatter = { let formatter = DateFormatter() formatter.calendar = Calendar(identifier: .iso8601) @@ -29,7 +29,7 @@ public extension Date { return formatter }() } - + /// Converts receiver to string with given style. /// /// Both date and time styles defaults to none, which means they will not be included in the string @@ -42,30 +42,30 @@ public extension Date { /// - full: "Monday, August 28, 2017", "2:40:52 PM Central European Summer Time" func toString(dateStyle: DateFormatter.Style = .none, timeStyle: DateFormatter.Style = .none, locale: Locale? = nil, timeZone: TimeZone? = nil) -> String { let dateFormatter: DateFormatter = DateFormatter() - + if let locale: Locale = locale { dateFormatter.locale = locale } if let timeZone: TimeZone = timeZone { dateFormatter.timeZone = timeZone } - + dateFormatter.timeStyle = timeStyle dateFormatter.dateStyle = dateStyle return dateFormatter.string(from: self) } - + /// Converts receiver to a string of the ISO8601 format. func asISO8601String() -> String { return Formatter.iso8601WithMs.string(from: self) } - - + + /// Constructs a Date instance based on a ISO8601 formatted string. static func dateFrom(iso8601String: String) -> Date? { return Formatter.iso8601WithMs.date(from: iso8601String) } - + /// Converts receiver to a string of the ISO8601 format. func asISO8601WithoutMsString() -> String { return Formatter.iso8601WithoutMs.string(from: self) @@ -75,17 +75,17 @@ public extension Date { static func dateFrom(iso8601StringWithoutMs: String) -> Date? { return Formatter.iso8601WithoutMs.date(from: iso8601StringWithoutMs) } - + /// Asks `Calendar.current` whether the instance is today. var isToday: Bool { return Calendar.current.isDateInToday(self) } - + /// Asks `Calendar.current` whether the instance is tomorrow. var isTomorrow: Bool { return Calendar.current.isDateInTomorrow(self) } - + /// Asks `Calendar.current` whether the instance is yesterday. var isYesterday: Bool { return Calendar.current.isDateInYesterday(self) @@ -107,7 +107,34 @@ public extension Date { } func date(byAdding component: Calendar.Component, value: Int) -> Date { - return Calendar.current.date(byAdding: component, value: value, to: self) ?? self + Calendar.current.date(byAdding: component, value: value, to: self) ?? self + } + + func date(bySubtracting component: Calendar.Component, value: Int) -> Date { + Calendar.current.date(byAdding: component, value: -value, to: self) ?? self + } + + /// Return a new `Date` by subtracting a `TimeInterval` to this `Date`. + /// + /// - parameter timeInterval: The value to add, in seconds. + func subtractingTimeInterval(_ timeInterval: TimeInterval) -> Date { + addingTimeInterval(-timeInterval) + } + + /// Return a new `Date` by adding a `TimeInterval` to date `Date`. + /// + /// - parameter date: the date to add to + /// - parameter timeInterval: The value to add, in seconds. + static func +(date: Date, timeInterval: TimeInterval) -> Date { + date.addingTimeInterval(timeInterval) + } + + /// Return a new `Date` by subtracting a `TimeInterval` from date `Date`. + /// + /// - parameter date: the date to subtract from + /// - parameter timeInterval: The value to subtract, in seconds. + static func -(date: Date, timeInterval: TimeInterval) -> Date { + date.subtractingTimeInterval(timeInterval) } } diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions/Dictionary/Dictionary.swift b/TriforkSwiftExtensions/TriforkSwiftExtensions/Dictionary/Dictionary.swift new file mode 100644 index 0000000..416c106 --- /dev/null +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions/Dictionary/Dictionary.swift @@ -0,0 +1,12 @@ +import Foundation + +extension Dictionary { + + func combine(other: [Key: Value]) -> [Key: Value] { + var result: Dictionary = self + other.forEach { (entry: (key: Key, value: Value)) in + result[entry.key] = entry.value + } + return result + } +} diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions/Error/ErrorExtension.swift b/TriforkSwiftExtensions/TriforkSwiftExtensions/Error/ErrorExtension.swift index 9d98135..ebcfa64 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensions/Error/ErrorExtension.swift +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions/Error/ErrorExtension.swift @@ -1,11 +1,3 @@ -// -// ErrorExtension.swift -// TriforkSwiftExtensions -// -// Created by Kim de Vos on 09/09/2019. -// Copyright © 2019 Trifork A/S. All rights reserved. -// - import Foundation extension Error { @@ -13,13 +5,32 @@ extension Error { /// Returns true if it's a network error public var isNetworkError: Bool { let error = self as NSError - let networkErrors = [NSURLErrorNetworkConnectionLost, - NSURLErrorNotConnectedToInternet, - NSURLErrorCannotLoadFromNetwork, - NSURLErrorSecureConnectionFailed, - NSURLErrorInternationalRoamingOff, - NSURLErrorTimedOut] + let networkErrors: [Int] = [NSURLErrorNetworkConnectionLost, + NSURLErrorNotConnectedToInternet, + NSURLErrorCannotLoadFromNetwork, + NSURLErrorSecureConnectionFailed, + NSURLErrorInternationalRoamingOff, + NSURLErrorTimedOut] return error.domain == NSURLErrorDomain && networkErrors.contains(error.code) } + + /// recursively gets all underlying error(s)'s description(s) + /// + /// - Returns: a string describing all errors including the underlying ones + func getDeepDescription( + separator: String = "| Underlying reason: " + ) -> String { + guard let underlyingError: Error = underlyingError else { + return localizedDescription + } + return localizedDescription + separator + underlyingError.getDeepDescription() + } + + /// - Returns: the "cause" / underlying reason for this error, if any exists. + public var underlyingError: Error? { + let nsError: NSError = self as NSError + return nsError.userInfo["NSUnderlyingError"] as? Error + } + } diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions/String/StringExtension.swift b/TriforkSwiftExtensions/TriforkSwiftExtensions/String/StringExtension.swift index 3607965..600e7a4 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensions/String/StringExtension.swift +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions/String/StringExtension.swift @@ -1,57 +1,45 @@ -// -// StringExtension.swift -// TriforkSwiftExtensions -// -// Created by Thomas Kalhøj Clemensen on 28/08/2017. -// Copyright © 2017 Trifork A/S. All rights reserved. -// - import Foundation public extension String { - + //MARK: - Encoding - + /// Returns a new and URL encoded instance of the receiver, without URL encoding query characters like :, ?, &, /, etc. var urlEncodedWithQuery: String { - return self.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? self + addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? self } - + /// Returns a url encoded strings with alphanumerics as allowed characters var urlEncoded: String { let allowedCharacterSet: CharacterSet = CharacterSet(charactersIn: "!*'();:@&=+$,/?%#[] ").inverted - return self.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? self + return addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? self } - + /// Returns a base64 encoded instance of the receiver. var base64Encoded: String? { - return self.data(using: .utf8)?.base64EncodedString() + data(using: .utf8)?.base64EncodedString() } - + /// Returns a string decoded from the base64 encoded receiver, returns `nil` if the receiver is not base64 encoded. var decodeBase64: String? { - let result: String? - if let data: Data = Data(base64Encoded: self, options: .ignoreUnknownCharacters) { - result = String(data: data, encoding: .utf8) - } - else { - result = nil + guard let validData: Data = Data(base64Encoded: self, options: .ignoreUnknownCharacters) else { + return nil } - return result + return String(data: validData, encoding: .utf8) } - + //MARK: - Conversion - + /// Creates a URL instance from the receiver. Returns `nil` if the string is an invalid URL. func toURL() -> URL? { - return URL(string: self) + URL(string: self) } - + /// Converts the receiver to UTF-8 encoded `Data` instance. func toData() -> Data? { - return self.data(using: .utf8) + data(using: .utf8) } - + //MARK: - RegEx /// Check if the receiver matches the regular expression defined in a string format. @@ -63,7 +51,7 @@ public extension String { func isMatching(regEx regExp: String, options: NSRegularExpression.Options = []) -> Bool { do { let matcher: NSRegularExpression = try NSRegularExpression(pattern: regExp, options: options) - let searchRange = NSRange(location: 0, length: self.length) + let searchRange = NSRange(location: 0, length: length) return matcher.firstMatch(in: self, options: [], range: searchRange) != nil } catch let error as NSError { TSELogger.log(message: "Unable to create regular expression from: \(regExp): \(error.localizedDescription)") @@ -81,33 +69,56 @@ public extension String { } return matcher.matches(in: self, options: [], range: NSRange(location: 0, length: self.count)) - .flatMap { result -> [String] in - var values = [String]() - for i in 0.. [String] in + var values = [String]() + for i in 0.. 0 + return detector?.matches(in: self, options: [], range: NSMakeRange(0, length)).count ?? 0 > 0 } - + /// Check if the string contains a valid email var isEmail: Bool { - return self.isMatching(regEx: "^(?:[a-z0-9!#$%\\&'*+/=?\\^_`{|}~-]+(?:\\.[a-z0-9!#$%\\&'*+/=?\\^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])$") + isMatching(regEx: "^(?:[a-z0-9!#$%\\&'*+/=?\\^_`{|}~-]+(?:\\.[a-z0-9!#$%\\&'*+/=?\\^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])$") } - + //MARK: - Other - + /// Returns the number of characters in the string. var length: Int { - return self.count + count + } + + + ///tells if this string only consists of whitespacesAndNewlines and is not empty + /// - See: CharacterSet.whitespacesAndNewlines + /// - Returns: true if this string only contains CharacterSet.whitespacesAndNewlines, false otherwise + func isBlank() -> Bool { + //if it has any "non whitespaces" then its not blank, and if it is nil, then only if it is empty is it blank. + isEmptyOrBlank() && isNotEmpty + } + + ///tells if this string is empty or only containing whitespacesAndNewlines + func isEmptyOrBlank() -> Bool { + //if it has any "non whitespaces" then its not blank, and if it is nil then either there are nothing (empty) or its only whitespace. + rangeOfCharacter(from: CharacterSet.whitespacesAndNewlines.inverted) == nil } + + ///tells if this is not empty (length = 0) or blank (only whitespaces and newlines) + func isNotEmptyOrBlank() -> Bool { + !isEmptyOrBlank() + } + } diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions/UIControl/Event/UIControlEvent.swift b/TriforkSwiftExtensions/TriforkSwiftExtensions/UIControl/Event/UIControlEvent.swift new file mode 100644 index 0000000..04f31c8 --- /dev/null +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions/UIControl/Event/UIControlEvent.swift @@ -0,0 +1,12 @@ +import Foundation +import UIKit + +extension UIControl.Event { + + static let touchEndedEvents: UIControl.Event = [ + UIControl.Event.touchUpInside, + UIControl.Event.touchCancel, + UIControl.Event.touchDragExit, + UIControl.Event.touchDragOutside + ] +} diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions/UIFont/UIFont.swift b/TriforkSwiftExtensions/TriforkSwiftExtensions/UIFont/UIFont.swift new file mode 100644 index 0000000..84ab24e --- /dev/null +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions/UIFont/UIFont.swift @@ -0,0 +1,12 @@ +import Foundation +import UIKit +extension UIFont { + + var isItalic: Bool { + fontDescriptor.symbolicTraits.contains(.traitItalic) + } + + var isBold: Bool { + fontDescriptor.symbolicTraits.contains(.traitBold) + } +} diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions/UITextField/UITextField.swift b/TriforkSwiftExtensions/TriforkSwiftExtensions/UITextField/UITextField.swift new file mode 100644 index 0000000..521ef6a --- /dev/null +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions/UITextField/UITextField.swift @@ -0,0 +1,15 @@ +import Foundation +import Foundation +import UIKit + +extension UITextField { + + func resignOnReturn() { + addTarget(self, action: #selector(onReturnResign), for: UIControl.Event.editingDidEndOnExit) + } + + @IBAction private func onReturnResign() { + resignFirstResponder() + } + +} diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions/UIView/UIViewExtension.swift b/TriforkSwiftExtensions/TriforkSwiftExtensions/UIView/UIViewExtension.swift index 4d1ce07..d1a3d88 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensions/UIView/UIViewExtension.swift +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions/UIView/UIViewExtension.swift @@ -142,4 +142,17 @@ public extension UIView { func show() { isHidden = false } + + /// Tells if this view is visible. + /// inverse of isHidden. + var isVisible: Bool { + get { + !isHidden + } + set { + isHidden = !newValue + } + } + + } From c260e1a486081f7c9eb2772c0bc215a153aa7591 Mon Sep 17 00:00:00 2001 From: Kasper Tvede Date: Fri, 31 Mar 2023 08:45:58 +0200 Subject: [PATCH 3/6] more simple extensions --- .../project.pbxproj | 12 +++++++++++ .../NotificationCenter.swift | 21 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 TriforkSwiftExtensions/TriforkSwiftExtensions/NotificationCenter/NotificationCenter.swift diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj b/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj index acc7497..6181b08 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 1A02D4EEEB24C7A1D8AB6327 /* UITextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02DF1C13CC7B2CF83E81B4 /* UITextField.swift */; }; 1A02D7CC5B56C4F33C7D5ED5 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02DEA45C9ABA4960CEB69E /* Dictionary.swift */; }; + 1A02D8EA90EAC07DEFEF7500 /* NotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02DD33BF566C0A5565C8FC /* NotificationCenter.swift */; }; 1A02DC5570DD8FA92F06126C /* UIControlEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02D9A55662C7C993CF01A2 /* UIControlEvent.swift */; }; 1A02DD8BA539DE7AE9A78D4F /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02D6CEB899DE021E506AC6 /* UIFont.swift */; }; C349B41C21639B2A007A4D37 /* CGRectExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C349B41B21639B2A007A4D37 /* CGRectExtension.swift */; }; @@ -99,6 +100,7 @@ /* Begin PBXFileReference section */ 1A02D6CEB899DE021E506AC6 /* UIFont.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = ""; }; 1A02D9A55662C7C993CF01A2 /* UIControlEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIControlEvent.swift; sourceTree = ""; }; + 1A02DD33BF566C0A5565C8FC /* NotificationCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationCenter.swift; sourceTree = ""; }; 1A02DEA45C9ABA4960CEB69E /* Dictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = ""; }; 1A02DF1C13CC7B2CF83E81B4 /* UITextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextField.swift; sourceTree = ""; }; C349B41B21639B2A007A4D37 /* CGRectExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGRectExtension.swift; sourceTree = ""; }; @@ -224,6 +226,14 @@ path = UIControl; sourceTree = ""; }; + 1A02DBD3761136FA792B236B /* NotificationCenter */ = { + isa = PBXGroup; + children = ( + 1A02DD33BF566C0A5565C8FC /* NotificationCenter.swift */, + ); + path = NotificationCenter; + sourceTree = ""; + }; 1A02DE048E6DCFD2782164D9 /* Event */ = { isa = PBXGroup; children = ( @@ -571,6 +581,7 @@ 1A02DEAFD393A617ABCCEC22 /* UIFont */, 1A02D1C835C6867C735D05DE /* UITextField */, 1A02D2825AEC120C99288629 /* UIControl */, + 1A02DBD3761136FA792B236B /* NotificationCenter */, ); path = TriforkSwiftExtensions; sourceTree = ""; @@ -805,6 +816,7 @@ 1A02DD8BA539DE7AE9A78D4F /* UIFont.swift in Sources */, 1A02D4EEEB24C7A1D8AB6327 /* UITextField.swift in Sources */, 1A02DC5570DD8FA92F06126C /* UIControlEvent.swift in Sources */, + 1A02D8EA90EAC07DEFEF7500 /* NotificationCenter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions/NotificationCenter/NotificationCenter.swift b/TriforkSwiftExtensions/TriforkSwiftExtensions/NotificationCenter/NotificationCenter.swift new file mode 100644 index 0000000..ee693b7 --- /dev/null +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions/NotificationCenter/NotificationCenter.swift @@ -0,0 +1,21 @@ +import Foundation + +extension NotificationCenter { + + /// sets the given observer. if there already is another equal observer it will be removed first + /// This avoids double registration + /// - Parameters: + /// - observer: the observer, see addObserver for more info + /// - selector: the callback function (selector), see addObserver for more info + /// - name: the observed name, see addObserver for more info + /// - object: the object to pass along on selector invocation, see addObserver for more info + func setObserver( + observer: AnyObject, + selector: Selector, + name: NSNotification.Name?, + object: AnyObject? + ) { + NotificationCenter.default.removeObserver(observer, name: name, object: object) + NotificationCenter.default.addObserver(observer, selector: selector, name: name, object: object) + } +} From e6c5ec356fb993dcf7fbc5e63078542224002456 Mon Sep 17 00:00:00 2001 From: Kasper Tvede Date: Wed, 5 Jul 2023 13:32:44 +0200 Subject: [PATCH 4/6] more extensions more cleanup --- .../project.pbxproj | 50 ++++++++++++++++--- .../xcschemes/TriforkSwiftExtensions.xcscheme | 2 +- .../Optional/OptionalExtension.swift | 17 ++++--- .../OptionalExtensionTests.swift | 22 +++----- 4 files changed, 60 insertions(+), 31 deletions(-) rename TriforkSwiftExtensions/TriforkSwiftExtensionsTests/{ => Optional}/OptionalExtensionTests.swift (77%) diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj b/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj index 6181b08..ca507ef 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -250,6 +250,14 @@ path = UIFont; sourceTree = ""; }; + 3CA554AB2A53F7F7007F3E68 /* Optional */ = { + isa = PBXGroup; + children = ( + E91351DC1F4EEF8400BC1A68 /* OptionalExtensionTests.swift */, + ); + path = Optional; + sourceTree = ""; + }; C349B41A21639B13007A4D37 /* CGRect */ = { isa = PBXGroup; children = ( @@ -589,6 +597,7 @@ E9DF34331F4D72E100BB36BB /* TriforkSwiftExtensionsTests */ = { isa = PBXGroup; children = ( + 3CA554AB2A53F7F7007F3E68 /* Optional */, C366946925607DBB00C34E28 /* PublisherTests.swift */, E9DF34361F4D72E100BB36BB /* Info.plist */, E9DF344A1F4D768F00BB36BB /* ArrayExtensionTests.swift */, @@ -606,7 +615,6 @@ C3A9C2ED21CCEE9F007DF893 /* IntExtensionTests.swift */, E9DF346D1F4DA2FB00BB36BB /* NSMutableAttributedStringExtensionTests.swift */, E92269091F53E1C000DAEFAE /* NSObjectExtensionTests.swift */, - E91351DC1F4EEF8400BC1A68 /* OptionalExtensionTests.swift */, E9EA2CCF227B021900944902 /* ResultTests.swift */, C3A5B7D62208D2DD009219A4 /* ReusableTests.swift */, C380F3A1254B5F3B00081B25 /* SequenceExtensionTests.swift */, @@ -715,8 +723,9 @@ E9DF341D1F4D72E100BB36BB /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0830; - LastUpgradeCheck = 1200; + LastUpgradeCheck = 1500; ORGANIZATIONNAME = "Trifork A/S"; TargetAttributes = { E9DF34251F4D72E100BB36BB = { @@ -908,6 +917,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -972,6 +982,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -983,7 +994,8 @@ IPHONEOS_DEPLOYMENT_TARGET = 10.3; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -1004,10 +1016,17 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = TriforkSwiftExtensions/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PRODUCT_BUNDLE_IDENTIFIER = com.trifork.TriforkSwiftExtensions; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1029,10 +1048,17 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = TriforkSwiftExtensions/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PRODUCT_BUNDLE_IDENTIFIER = com.trifork.TriforkSwiftExtensions; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1050,7 +1076,11 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ZZGMPFLA2F; INFOPLIST_FILE = TriforkSwiftExtensionsTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.trifork.TriforkSwiftExtensionsTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1068,7 +1098,11 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ZZGMPFLA2F; INFOPLIST_FILE = TriforkSwiftExtensionsTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.trifork.TriforkSwiftExtensionsTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/xcshareddata/xcschemes/TriforkSwiftExtensions.xcscheme b/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/xcshareddata/xcschemes/TriforkSwiftExtensions.xcscheme index 7971a0a..bacd1eb 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/xcshareddata/xcschemes/TriforkSwiftExtensions.xcscheme +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/xcshareddata/xcschemes/TriforkSwiftExtensions.xcscheme @@ -1,6 +1,6 @@ ()) { - switch self { - case .some(let wrapped): + if let wrapped = self { doBlock(wrapped) - case .none: - break + } + } + + /// Unwraps a double nilable value into a single nilable value. + func flattern() -> T? where Wrapped == T? { + return flatMap { (wrapped: Wrapped) in + wrapped } } } @@ -44,3 +44,4 @@ public extension Optional where Wrapped: Collection { return self?.isEmpty ?? true } } + diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/OptionalExtensionTests.swift b/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/Optional/OptionalExtensionTests.swift similarity index 77% rename from TriforkSwiftExtensions/TriforkSwiftExtensionsTests/OptionalExtensionTests.swift rename to TriforkSwiftExtensions/TriforkSwiftExtensionsTests/Optional/OptionalExtensionTests.swift index 417238f..9e2711e 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/OptionalExtensionTests.swift +++ b/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/Optional/OptionalExtensionTests.swift @@ -1,26 +1,12 @@ // // OptionalExtensionTests.swift // TriforkSwiftExtensions -// -// Created by Kasper Martin Tvede on 24/08/2017. -// Copyright © 2017 Trifork A/S. All rights reserved. -// import XCTest @testable import TriforkSwiftExtensions class OptionalExtensionTests: XCTestCase { - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - func testPassTo(){ var receiver : String? = "" @@ -62,4 +48,12 @@ class OptionalExtensionTests: XCTestCase { XCTAssertFalse(optionalCollection.isNilOrEmpty) } + func testFlattern() { + let fullNil: String?? = nil + XCTAssertNil(fullNil.flattern()) + + let notNil: String?? = "test" + XCTAssertEqual("test",notNil.flattern()) + } + } From 64d7974bb174bcf1f6fa08ea8f41b853e94ebed5 Mon Sep 17 00:00:00 2001 From: Kasper Tvede Date: Thu, 6 Jul 2023 12:30:11 +0200 Subject: [PATCH 5/6] more extensions --- .../project.pbxproj | 10 ++++-- .../Dictionary/Dictionary.swift | 35 +++++++++++++++++-- .../Optional/OptionalExtension.swift | 4 --- .../Sequence/Categorization.swift | 14 ++++++++ .../Optional/OptionalExtensionTests.swift | 4 --- 5 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 TriforkSwiftExtensions/TriforkSwiftExtensions/Sequence/Categorization.swift diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj b/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj index ca507ef..7d6e7cf 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 1A02D8EA90EAC07DEFEF7500 /* NotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02DD33BF566C0A5565C8FC /* NotificationCenter.swift */; }; 1A02DC5570DD8FA92F06126C /* UIControlEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02D9A55662C7C993CF01A2 /* UIControlEvent.swift */; }; 1A02DD8BA539DE7AE9A78D4F /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02D6CEB899DE021E506AC6 /* UIFont.swift */; }; + 3C5B005B2A56CCA7009878A0 /* Categorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5B005A2A56CCA7009878A0 /* Categorization.swift */; }; C349B41C21639B2A007A4D37 /* CGRectExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C349B41B21639B2A007A4D37 /* CGRectExtension.swift */; }; C349B41E21639B5C007A4D37 /* CGRectExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C349B41D21639B5C007A4D37 /* CGRectExtensionTests.swift */; }; C35BD0D22272EB8E00B24F3B /* UITextViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35BD0D12272EB8E00B24F3B /* UITextViewExtensions.swift */; }; @@ -103,6 +104,7 @@ 1A02DD33BF566C0A5565C8FC /* NotificationCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationCenter.swift; sourceTree = ""; }; 1A02DEA45C9ABA4960CEB69E /* Dictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = ""; }; 1A02DF1C13CC7B2CF83E81B4 /* UITextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextField.swift; sourceTree = ""; }; + 3C5B005A2A56CCA7009878A0 /* Categorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Categorization.swift; sourceTree = ""; }; C349B41B21639B2A007A4D37 /* CGRectExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGRectExtension.swift; sourceTree = ""; }; C349B41D21639B5C007A4D37 /* CGRectExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGRectExtensionTests.swift; sourceTree = ""; }; C35BD0D12272EB8E00B24F3B /* UITextViewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextViewExtensions.swift; sourceTree = ""; }; @@ -318,6 +320,7 @@ isa = PBXGroup; children = ( C380F39D254B5EE800081B25 /* SequenceExtensions.swift */, + 3C5B005A2A56CCA7009878A0 /* Categorization.swift */, ); path = Sequence; sourceTree = ""; @@ -557,6 +560,7 @@ E9DF34421F4D743900BB36BB /* Collection */, E922691A1F5441B900DAEFAE /* Data */, E922691F1F54458D00DAEFAE /* Date */, + 1A02D0BE082706696DFBE88A /* Dictionary */, E97F00071FE2653100BB262F /* Encodable */, C3D20F9A232633A600DE4845 /* Error */, C35C0C4C229E782000B6B88A /* FloatingPoint */, @@ -564,12 +568,14 @@ E9DF346A1F4DA22B00BB36BB /* NSAttributedString */, E92269061F53E18700DAEFAE /* NSObject */, C3A9C2EA21CCED2F007DF893 /* Numeric */, + 1A02DBD3761136FA792B236B /* NotificationCenter */, E91351D81F4EED7400BC1A68 /* Optional */, C366946425607DAD00C34E28 /* Publisher */, E9EA2CCC227AF3ED00944902 /* Result */, C380F39C254B5EDC00081B25 /* Sequence */, E92269151F53F93300DAEFAE /* String */, E92269461F554FC500DAEFAE /* TriforkSwiftExtensionUtilities */, + E92269241F5450F900DAEFAE /* URL */, E92269291F5459B200DAEFAE /* UIAlertController */, E946D3CF20FDE274002FFDFF /* UIApplication */, E91E19DE1F7D128E00F38282 /* UIColor */, @@ -584,12 +590,9 @@ C35BD0D02272EB8E00B24F3B /* UITextView */, E922693C1F5547C400DAEFAE /* UIView */, E922692C1F545B4C00DAEFAE /* UIViewController */, - E92269241F5450F900DAEFAE /* URL */, - 1A02D0BE082706696DFBE88A /* Dictionary */, 1A02DEAFD393A617ABCCEC22 /* UIFont */, 1A02D1C835C6867C735D05DE /* UITextField */, 1A02D2825AEC120C99288629 /* UIControl */, - 1A02DBD3761136FA792B236B /* NotificationCenter */, ); path = TriforkSwiftExtensions; sourceTree = ""; @@ -789,6 +792,7 @@ C35BD0D22272EB8E00B24F3B /* UITextViewExtensions.swift in Sources */, E92269211F5445A400DAEFAE /* DateExtension.swift in Sources */, E92269081F53E19F00DAEFAE /* NSObjectExtension.swift in Sources */, + 3C5B005B2A56CCA7009878A0 /* Categorization.swift in Sources */, E937DFCC21DCBD2000DE4E8C /* UILabelExtensions.swift in Sources */, E946D3D220FDE28D002FFDFF /* UIApplicationExtensions.swift in Sources */, E91E19E01F7D129B00F38282 /* UIColorExtension.swift in Sources */, diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions/Dictionary/Dictionary.swift b/TriforkSwiftExtensions/TriforkSwiftExtensions/Dictionary/Dictionary.swift index 416c106..d810673 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensions/Dictionary/Dictionary.swift +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions/Dictionary/Dictionary.swift @@ -2,11 +2,42 @@ import Foundation extension Dictionary { - func combine(other: [Key: Value]) -> [Key: Value] { + ///Combines 2 dictionarys. + ///On collisions will overwrite with the other dictionary + func combineOverwrite(other: [Key: Value]) -> [Key: Value] { + combine(other: other) { (key: Key, lhs: Value, rhs: Value) in + rhs + } + } + + ///Combines 2 dictionarys. + ///On collisions will perform the "onCollision" to resolve the resulting value + func combine( + other: [Key: Value], + onCollision: (_ key: Key,_ lhs: Value,_ rhs: Value) -> Value + ) -> [Key: Value] { var result: Dictionary = self other.forEach { (entry: (key: Key, value: Value)) in - result[entry.key] = entry.value + let newValue: Value + if let existingValue = result[entry.key] { + newValue = onCollision(entry.key, existingValue, entry.value) + } else { + newValue = entry.value + } + result[entry.key] = newValue } return result } + + + /// Adds the given element to the list by the key, or puts it into the dictionary + mutating func addOrPut( + key: Key, + element: T + ) where Value == [T] { + var result: [T] = self[key] ?? [] + result.append(element) + self[key] = result + } + } diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions/Optional/OptionalExtension.swift b/TriforkSwiftExtensions/TriforkSwiftExtensions/Optional/OptionalExtension.swift index c9faf67..bcb7296 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensions/Optional/OptionalExtension.swift +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions/Optional/OptionalExtension.swift @@ -1,7 +1,3 @@ -// -// OptionalExtension.swift -// TriforkSwiftExtensions - import Foundation extension Optional { diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions/Sequence/Categorization.swift b/TriforkSwiftExtensions/TriforkSwiftExtensions/Sequence/Categorization.swift new file mode 100644 index 0000000..e7993d3 --- /dev/null +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions/Sequence/Categorization.swift @@ -0,0 +1,14 @@ +import Foundation +extension Sequence { + + /// Categorizes this sequence by extracting the key and adding the given element to that list. + func categorize(by getKeyBy: (Element) -> Key) -> [Key: [Element]] { + var categorization: [Key: [Element]] = [:] + forEach { (item: Element) in + let key = getKeyBy(item) + categorization.addOrPut(key: key, element: item) + } + return categorization + } + +} diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/Optional/OptionalExtensionTests.swift b/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/Optional/OptionalExtensionTests.swift index 9e2711e..306e949 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/Optional/OptionalExtensionTests.swift +++ b/TriforkSwiftExtensions/TriforkSwiftExtensionsTests/Optional/OptionalExtensionTests.swift @@ -1,7 +1,3 @@ -// -// OptionalExtensionTests.swift -// TriforkSwiftExtensions - import XCTest @testable import TriforkSwiftExtensions From b6cee002a08faf61c7e04beb006e09c7c99a3ce0 Mon Sep 17 00:00:00 2001 From: Kasper Tvede Date: Thu, 6 Jul 2023 15:31:59 +0200 Subject: [PATCH 6/6] more extensions --- .../Sequence/SequenceExtensions.swift | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/TriforkSwiftExtensions/TriforkSwiftExtensions/Sequence/SequenceExtensions.swift b/TriforkSwiftExtensions/TriforkSwiftExtensions/Sequence/SequenceExtensions.swift index 36b24b8..b7e70c8 100644 --- a/TriforkSwiftExtensions/TriforkSwiftExtensions/Sequence/SequenceExtensions.swift +++ b/TriforkSwiftExtensions/TriforkSwiftExtensions/Sequence/SequenceExtensions.swift @@ -39,4 +39,38 @@ extension Sequence { comparator(a[keyPath: keyPath], b[keyPath: keyPath]) } } + + func toArray() -> [Element] { + map { (element: Element) in + element + } + } + + ///Filters all nil values away + func filterNotNil() -> [T] where Element == T? { + compactMap { (element: Element) in + element + } + } + + ///Creates a updated array of elements from this sequence, and updates those that matches the given predicate, by the given updateBy action + func updated( + with: any Sequence, + predicate: (_ oldElement: Element,_ newElement: Element) -> Bool, + updateBy: (_ oldElement: Element,_ newElement: Element) -> Element + ) -> [Element] { + var result: [Element] = self.toArray() + + with.forEach { (newElement: Element) in + let potentialIndex = result.firstIndex { (oldElement: Element) in + predicate(oldElement, newElement) + } + guard let toUpdateIndex = potentialIndex else { + return + } + result[toUpdateIndex] = updateBy(result[toUpdateIndex], newElement) + } + + return result + } }