Swiftpack.co - Package - Flinesoft/HandySwift

Build Status Code Quality Coverage Version: 3.2.0 Swift: 5.1 Platforms: iOS | tvOS | macOS | Linux License: MIT
PayPal: Donate GitHub: Become a sponsor Patreon: Become a patron

InstallationUsageDonationIssuesContributingLicense

HandySwift

The goal of this library is to provide handy features that didn't make it to the Swift standard library (yet) due to many different reasons. Those could be that the Swift community wants to keep the standard library clean and manageable or simply hasn't finished discussion on a specific feature yet.

If you like this, please also checkout HandyUIKit for handy UI features that we feel should have been part of the UIKit frameworks in the first place.

If you are upgrading from a previous major version of HandySwift (e.g. 1.x to 2.x) then checkout the releases section on GitHub and look out for the release notes of the last major releas(es) (e.g. 2.0.0) for an overview of the changes made. It'll save you time as hints are on how best to migrate are included there.

Installation

Currently the recommended way of installing this library is via Swift Package Manager. Carthage & Cocoapods are supported, too.

You can of course also just include this framework manually into your project by downloading it or by using git submodules.

Usage

Please have a look at the UsageExamples.playground for a complete list of features provided. Open the Playground from within the .xcworkspace in order for it to work.


Feature Overview


Globals

Some global helpers.

delay(bySeconds:) { ... }

Runs a given closure after a delay given in seconds. Dispatch queue can be set optionally, defaults to Main thread.

delay(by: .milliseconds(1_500)) { // Runs in Main thread by default
    date = NSDate() // Delayed by 1.5 seconds: 2016-06-07 05:38:05 +0000
}
delay(by: .seconds(5), dispatchLevel: .userInteractive) {
    date = NSDate() // Delayed by 5 seconds: 2016-06-07 05:38:08 +0000
}

IntExtension

init(randomBelow:)

Initialize random Int value below given positive value.

Int(randomBelow: 50)! // => 26
Int(randomBelow: 1_000_000)! // => 208041

.times

Repeat some code block a given number of times.

3.times { print("Hello World!") }
// => prints "Hello World!" 3 times

.timesMake

Makes array by adding closure's return value n times.

let intArray = 5.timesMake { Int(randomBelow: 1_000)! }
// => [481, 16, 680, 87, 912]

ComparableExtension

clamped(to:)

Apply a limiting range as the bounds of a Comparable. Supports ClosedRange (a ... b), PartialRangeFrom (a...) and PartialRangeThrough (...b) as the limits.

let myNum = 3
myNum.clamped(to: 0 ... 6) // => 3
myNum.clamped(to: 0 ... 2) // => 2
myNum.clamped(to: 4 ... 6) // => 4
myNum.clamped(to: 5...) // => 4
myNum.clamped(to: ...2) // => 2

let myString = "d"
myString.clamped(to: "a" ... "g") // => "d"
myString.clamped(to: "a" ... "c") // => "c"
myString.clamped(to: "e" ... "g") // => "e"
myString.clamped(to: "f"...) // => "f"
myString.clamped(to: ..."c") // => "c"

clamp(to:)

In-place mutating variant of clamped(to:)

var myNum = 3
myNum.clamp(to: 0...2)
myNum // => 2

StringExtension

.stripped()

Returns string with whitespace characters stripped from start and end.

" \n\t BB-8 likes Rey \t\n ".stripped()
// => "BB-8 likes Rey"

.isBlank

Checks if String contains any characters other than whitespace characters.

"  \t  ".isBlank
// => true

init(randomWithLength:allowedCharactersType:)

Get random numeric/alphabetic/alphanumeric String of given length.

String(randomWithLength: 4, allowedCharactersType: .numeric) // => "8503"
String(randomWithLength: 6, allowedCharactersType: .alphabetic) // => "ysTUzU"
String(randomWithLength: 8, allowedCharactersType: .alphaNumeric) // => "2TgM5sUG"
String(randomWithLength: 10, allowedCharactersType: .allCharactersIn("?!🐲🍏✈️🎎🍜"))
// => "!🍏🐲✈️🎎🐲🍜??🍜"

.fullRange

Get the full Range on a String object.

let unicodeString = "Hello composed unicode symbols! 👨‍👩‍👧‍👦👨‍👨‍👦‍👦👩‍👩‍👧‍👧"
unicodeString[unicodeString.fullRange] // => same string

NSRangeExtension

init(_:in:)

Converting from NSRange to Range<String.Index> became simple in Swift 4:

let string = "Hello World!"
let nsRange = NSRange(location: 0, length: 10)
let swiftRange = Range(nsRange, in: string)

The opposite is now also possible with this extension:

let string = "Hello World!"
let swiftRange: Range<String.Index> = string.fullRange
let nsRange = NSRange(swiftRange, in: string)

ArrayExtension

.sample

Returns a random element within the array or nil if array empty.

[1, 2, 3, 4, 5].sample // => 4
([] as [Int]).sample // => nil

.sample(size:)

Returns an array with size random elements or nil if array empty.

[1, 2, 3, 4, 5].sample(size: 3) // => [2, 1, 4]
[1, 2, 3, 4, 5].sample(size: 8) // => [1, 4, 2, 4, 3, 4, 1, 5]
([] as [Int]).sample(size: 3) // => nil

.combinations(with:)

Combines each element with each element of a given other array.

[1, 2, 3].combinations(with: ["A", "B"])
// => [(1, "A"), (1, "B"), (2, "A"), (2, "B"), (3, "A"), (3, "B")]

DictionaryExtension

init?(keys:values:)

Initializes a new Dictionary and fills it with keys and values arrays or returns nil if count of arrays differ.

let structure = ["firstName", "lastName"]
let dataEntries = [["Harry", "Potter"], ["Hermione", "Granger"], ["Ron", "Weasley"]]
Dictionary(keys: structure, values: dataEntries[0]) // => ["firstName": "Harry", "lastName": "Potter"]

dataEntries.map { Dictionary(keys: structure, values: $0) }
// => [["firstName": "Harry", "lastName": "Potter"], ["firstName": "Hermione", "lastName": "Grange"], ...]

Dictionary(keys: [1,2,3], values: [1,2,3,4,5]) // => nil

.merge(Dictionary)

Merges a given Dictionary into an existing Dictionary overriding existing values for matching keys.

var dict = ["A": "A value", "B": "Old B value"]
dict.merge(["B": "New B value", "C": "C value"])
dict // => ["A": "A value", "B": "New B value", "C": "C value"]

.merged(with: Dictionary)

Create new merged Dictionary with the given Dictionary merged into a Dictionary overriding existing values for matching keys.

let immutableDict = ["A": "A value", "B": "Old B value"]
immutableDict.merged(with: ["B": "New B value", "C": "C value"])
// => ["A": "A value", "B": "New B value", "C": "C value"]

DispatchTimeIntervalExtension

.timeInterval

Returns a TimeInterval object from a DispatchTimeInterval.

DispatchTimeInterval.milliseconds(500).timeInterval // => 0.5

TimeIntervalExtension

Unit based pseudo-initializers

Returns a TimeInterval object with a given value in a the specified unit.

TimeInterval.days(1.5) // => 129600
TimeInterval.hours(1.5) // => 5400
TimeInterval.minutes(1.5) // => 90
TimeInterval.seconds(1.5) // => 1.5
TimeInterval.milliseconds(1.5) // => 0.0015
TimeInterval.microseconds(1.5) // => 1.5e-06
TimeInterval.nanoseconds(1.5) // => 1.5e-09

Unit based getters

Returns a double value with the time interval converted to the specified unit.

let timeInterval: TimeInterval = 60 * 60 * 6

timeInterval.days // => 0.25
timeInterval.hours // => 6
timeInterval.minutes // => 360
timeInterval.seconds // => 21600
timeInterval.milliseconds // => 21600000
timeInterval.microseconds // => 21600000000
timeInterval.nanoseconds // => 21600000000000

SortedArray

The main purpose of this wrapper is to provide speed improvements for specific actions on sorted arrays.

init(array:) & .array

let unsortedArray = [5, 2, 1, 3, 0, 4]
let sortedArray = SortedArray(unsortedArray)
sortedArray.array   // => [0, 1, 2, 3, 4, 5]

.index

Finds the lowest index matching the given predicate using binary search for an improved performance (O(log n)).

SortedArray([5, 2, 1, 3, 0, 4]).index { $0 > 1 }
// => 2

.prefix(upTo:) / .prefix(through:)

SortedArray([5, 2, 1, 3, 0, 4]).prefix(upTo: 2)
// => [0, 1]

.suffix(from:)

SortedArray([5, 2, 1, 3, 0, 4]).suffix(from: 2)
// => [2, 3, 4, 5]

FrequencyTable

FrequencyTable(values: valuesArray) { valueToFrequencyClosure }

Initialize with values and closure.

struct WordFrequency {
    let word: String; let frequency: Int
    init(word: String, frequency: Int) { self.word = word; self.frequency = frequency }
}
let wordFrequencies = [
    WordFrequency(word: "Harry", frequency: 10),
    WordFrequency(word: "Hermione", frequency: 4),
    WordFrequency(word: "Ronald", frequency: 1)
]

let frequencyTable = FrequencyTable(values: wordFrequencies) { $0.frequency }
// => HandySwift.FrequencyTable<WordFrequency>

.sample

Returns a random element with frequency-based probability within the array or nil if array empty.

frequencyTable.sample
let randomWord = frequencyTable.sample.map { $0.word }
// => "Harry"

.sample(size:)

Returns an array with size frequency-based random elements or nil if array empty.

frequencyTable.sample(size: 6)
let randomWords = frequencyTable.sample(size: 6)!.map { $0.word }
// => ["Harry", "Ronald", "Harry", "Harry", "Hermione", "Hermione"]

Regex

Regex is a swifty regex engine built on top of the NSRegularExpression API.

init(_:options:)

Initialize with pattern and, optionally, options.

let regex = try Regex("(Phil|John), [\\d]{4}")

let options: Regex.Options = [.ignoreCase, .anchorsMatchLines, .dotMatchesLineSeparators, .ignoreMetacharacters]
let regexWithOptions = try Regex("(Phil|John), [\\d]{4}", options: options)

regex.matches(_:)

Checks whether regex matches string

regex.matches("Phil, 1991") // => true

regex.matches(in:)

Returns all matches

regex.matches(in: "Phil, 1991 and John, 1985")  
// => [Match<"Phil, 1991">, Match<"John, 1985">]

regex.firstMatch(in:)

Returns first match if any

regex.firstMatch(in: "Phil, 1991 and John, 1985")
// => Match<"Phil, 1991">

regex.replacingMatches(in:with:count:)

Replaces all matches in a string with a template string, up to the a maximum of matches (count).

regex.replacingMatches(in: "Phil, 1991 and John, 1985", with: "$1 was born in $2", count: 2)
// => "Phil was born in 1991 and John was born in 1985"

match.string

Returns the captured string

match.string // => "Phil, 1991"

match.range

Returns the range of the captured string within the base string

match.range // => Range

match.captures

Returns the capture groups of a match

match.captures // => ["Phil", "1991"]

match.string(applyingTemplate:)

Replaces the matched string with a template string

match.string(applyingTemplate: "$1 was born in $2")
// => "Phil was born in 1991"

Weak

Weak is a wrapper to store weak references to a Wrapped instance.

Weak(_:)

Initialize with an object reference.

let text: NSString = "Hello World!"
var weak = Weak(text)

Accessing inner Reference

Access the inner wrapped reference with the value property.

print(weak.value!)

NilLiteralExpressible Conformance

Create a Weak wrapper by assigning nil to the value.

var weakWrappedValue: Weak<AnyObject> = nil

Unowned

Unowned is a wrapper to store unowned references to a Wrapped instance.

Unowned(_:)

Initialize with an object reference.

var unowned = Unowned(text)

Accessing inner Reference

Access the inner wrapped reference with the value property.

print(unowned.value)

CollectionExtension

[try:]

Returns an element with the specified index or nil if the element does not exist .

let testArray = [0, 1, 2, 3, 20]
testArray[try: 4]  // => Optional(20)
testArray[try: 20] // => nil

.sum()

Returns the sum of all elements. The return type is determined by the numeric elements, e.g. Int for [Int]. NOTE: Only available for Numeric types.

[0, 1, 2, 3, 4].sum() // => 10
[0.5, 1.5, 2.5].sum() // => 4.5

.average()

Returns the average of all elements as a Double value. NOTE: Only available for Int and Double collections.

[10, 20, 30, 40].average() // => 25.0
[10.75, 20.75, 30.25, 40.25].average() // => 25.5

Withable

Simple protocol to make constructing and modifying objects with multiple properties more pleasant (functional, chainable, point-free). Supported by all NSObject subclasses by default.

struct Foo: Withable {
    var bar: Int = 0
    var baz: Bool = false
}

// Construct a foo, setting an arbitrary subset of properties
let foo = Foo { $0.bar = 5 }

// Make a copy of foo, overriding an arbitrary subset of properties
let foo2 = foo.with { $0.bar = 7; $0.baz = true }

foo.bar // => 5
foo2.bar // => 7

Donation

BartyCrouch was brought to you by Cihat Gündüz in his free time. If you want to thank me and support the development of this project, please make a small donation on PayPal. In case you also like my other open source contributions and articles, please consider motivating me by becoming a sponsor on GitHub or a patron on Patreon.

Thank you very much for any donation, it really helps out a lot! 💯

Contributing

Contributions are welcome. Feel free to open an issue on GitHub with your ideas or implement an idea yourself and post a pull request. If you want to contribute code, please try to follow the same syntax and semantic in your commit messages (see rationale here). Also, please make sure to add an entry to the CHANGELOG.md file which explains your change.

License

This library is released under the MIT License. See LICENSE for details.

Github

link
Stars: 352

Dependencies

Used By

Total: 0

Releases

- 2020-03-27 12:15:03

Added

  • New DivisibleArithmetic protocol which easily extends average() to Collections of Double, Float and CGFloat.
    Issue: #36 | PR: #38 | Author: David Knothe
  • Make most of the API @inlinable for increased real-time performance.
    Issue: #40 | PR: #43 | Author: David Knothe

Changed

  • Allow Int.init?(randomBelow:) to use an arbitrary RandomNumberGenerator (instead of just the system one).
    PR: #44 | Author: David Knothe

- 2020-03-27 12:14:44

Added

  • New Comparable.clamped(to:) and Comparable.clamp(to:) interfaces for any Comparable, e. g. Int.

- 2019-05-02 07:03:42

Addedge

  • New Withable protocol to init/copy objects and set properties in a convenient way on a single line.

Changed

  • Upgraded to Swift 5 & Xcode 10.2.

Removed

  • Remove ExpressibleByStringLiteral conformance of Regex type to only allow initialization via init(_:options:) throws interface.

- 2019-02-11 16:46:27

Added

  • New NSRange(_:in:) initializer for converting from Range<String.Index>
  • New sum computed property on Sequence types like Array
  • New average computed property on Collection types with Int or Double elements like [Int]
  • New fullRange and fullNSRange computed properties on String

Changed

  • Made some APIs available in wider contexts (like sample in RandomAccessCollection instead of Array)

Deprecated

  • None.

Removed

  • None.

Fixed

  • None.

Security

  • None.

- 2018-04-22 14:35:13

New Features

  • New swifty Regex type built on top of the NSRegularExpression API.

2.4.0 - 2017-09-26 08:26:47

Update to Swift 4 & Xcode 9

2.3.0 - 2017-04-26 12:56:05

New Features

  • New sort(stable:), sorted(stable:), sort(by:stable:) and sorted(by:stable:) methods for Array type to support stable sorting in Swift

2.2.0 - 2017-02-18 15:53:09

New Features

  • A new TimeInterval extension with unit based pseudo-init methods and getters

2.1.0 - 2017-02-13 16:10:27

New Features

  • A new DispatchTimeInterval extension with timeInterval conversion method

2.0.0 - 2017-01-30 22:34:50

New Features

  • A SortedArray can now be initialized with any Sequence type
  • New mutating methods were added, like insert(newElement:) and insert(contentsOf:)

Changed / Renamed Features

  • Changes to reflect naming scheme in Array class in the class SortedArray, namely:
    • firstMatchingIndex() was renamed to index(where:)
    • subArray(toIndex:) was renamed to prefix(upTo:)
    • subArray(fromIndex:) was renamed to suffix(from:)
  • The global delay() method now takes a typealiased DispatchTimeInterval and QOS class (optional)
  • Int(randomBelow:) now is an optional initializer instead of returning 0 in error cases
  • Some more small naming changes according to the Great Swift 3 Rename
    • e.g. .strip was renamed to .stripped(), .mergedWith() to .merged(with:) etc.

Removed Features

  • UI-related extensions were extracted to their own project: HandyUIKit, namely:
    • UIColor extension
    • CoreGraphics extensions (CGRect, CGPoint, CGSize)
  • The global delay method can't take .main as its dispatch level
    • instead it defaults to the .main thread if no QOS class is specified

1.4.0 - 2017-01-11 17:05:33

New:

  • combinations(with:) method on Arrays for creating cartesian products

1.3.0 - 2016-09-18 11:05:47

Updates the entire project to be compatible with Xcode 8 and Swift 3.

1.1.0 - 2016-06-07 06:03:51

New:

  • Global delay(bySeconds:) { ... } method with optional dispatch level

Removed:

  • Support for OS X 10.9 (10.10+ is the new requirement)

1.0.0 - 2016-04-17 20:26:39

This version adds the ColorExtension for iOS & tvOS targets which helps on some common access and change tasks using UIColor. See the README for further documentation.

Please note that this version was also marked as 1.0 according to recommendations on the SemVer 2.0 website as this library is stable, well tested and already used in production.

0.8.2 - 2016-03-22 07:51:55

0.8 - 2016-03-20 13:26:32

Add .merge() method to DictionaryExtension.

- 2016-01-16 13:43:54

Add DictionaryExtension with init?(keys:values:)

- 2016-01-03 00:43:21

Add frequency-based sample methods via struct.

- 2015-12-29 22:08:15

Add randomizing features + tvOS/OSX support.

0.4 - 2015-12-26 15:45:55

Add ArrayExtension with .sample methods

0.3 - 2015-12-26 14:16:10

StringExtension with .strip and .isBlank

- 2015-12-26 02:19:16

- 2015-12-26 01:39:18

Add SortedArray struct with binary search.

- 2015-12-25 23:10:34

Restructured + Swift Package Manager ready.

- 2015-12-20 01:28:25

Moved times method to Int class + example improvements.

- 2015-12-19 23:40:12

Remove ambiguous times method with argument.

Fix deployment target issue. - 2015-12-19 13:07:55

Lowered deployment target to iOS 8.0 - 2015-12-19 12:51:18

0.1 - 2015-12-18 22:54:39

Support for .times method on IntegerType – .times Method supported – Carthage install supported