{"id":13355861,"url":"https://github.com/tbaranes/SwiftyUtils","last_synced_at":"2025-03-12T11:30:54.967Z","repository":{"id":7961008,"uuid":"56979564","full_name":"tbaranes/SwiftyUtils","owner":"tbaranes","description":"All the reusable code that we need in each project","archived":false,"fork":false,"pushed_at":"2023-07-24T07:00:23.000Z","size":911,"stargazers_count":556,"open_issues_count":1,"forks_count":39,"subscribers_count":10,"default_branch":"master","last_synced_at":"2024-10-30T03:42:35.373Z","etag":null,"topics":["extensions","ios","macos","sugar","swift","tvos","watchos"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tbaranes.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-04-24T15:39:02.000Z","updated_at":"2024-10-22T12:55:59.000Z","dependencies_parsed_at":"2024-01-02T21:28:14.716Z","dependency_job_id":null,"html_url":"https://github.com/tbaranes/SwiftyUtils","commit_stats":{"total_commits":406,"total_committers":8,"mean_commits":50.75,"dds":"0.28817733990147787","last_synced_commit":"ba11b0a45c39c454636dedc1feed991ca071772c"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tbaranes%2FSwiftyUtils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tbaranes%2FSwiftyUtils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tbaranes%2FSwiftyUtils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tbaranes%2FSwiftyUtils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tbaranes","download_url":"https://codeload.github.com/tbaranes/SwiftyUtils/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243208791,"owners_count":20254106,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["extensions","ios","macos","sugar","swift","tvos","watchos"],"created_at":"2024-07-29T21:02:26.955Z","updated_at":"2025-03-12T11:30:54.310Z","avatar_url":"https://github.com/tbaranes.png","language":"Swift","readme":"# SwiftyUtils\n\n[![CI Status](https://travis-ci.org/tbaranes/SwiftyUtils.svg)](https://travis-ci.org/tbaranes/SwiftyUtils)\n![Language](https://img.shields.io/badge/language-Swift%205.0-orange.svg)\n[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/SwiftyUtils.svg)](https://img.shields.io/cocoapods/v/SwiftyUtils.svg)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![Platform](https://img.shields.io/cocoapods/p/SwiftyUtils.svg?style=flat)](http://cocoadocs.org/docsets/SwiftyUtils)\n\nSwiftyUtils groups all the reusable code that we need to ship in each project. This framework contains:\n- Extensions\n- Protocols\n- Structs\n- Subclasses\n\nWorking on iOS, macOS, tvOS, and watchOS, everything has been made to be easy to use! :tada:\n\n## Contents\n\nCheck out the repository to find examples / tests for each feature.\n\n**Swift, Foundation and CoreGraphics extensions:**\n\n - [Array](#array-extension)\n - [Bundle](#bundle-extension)\n - [CGFloat](#cgfloat-extension)\n - [CGPoint](#cgpoint-extension)\n - [CGRect](#cgrect-extension)\n - [CGSize](#cgsize-extension)\n - [Color](#color-extension)\n - [Date](#date-extension)\n - [Dictionary](#dictionary-extension)\n - [Double](#double-extension)\n - [FileManager](#filemanager-extension)\n - [Int](#int-extension)\n - [MutableCollection](#mutablecollection-extension)\n - [NotificationCenter](#notificationcenter-extension)\n - [NSAttributedString](#nsattributedstring-extension)\n - [NSLayoutConstraint](#nslayoutconstraint-extension)\n - [NSMutableAttributedString](#nsmutableattributedstring-extension)\n - [NSObject](#nsobject-extension)\n - [NSRange](#nsrange-extension)\n - [ReusableFormatters](#reusableformatters)\n - [Sequence](#sequence-extension)\n - [String](#string-extension)\n - [Timer](#timer-extension)\n - [URL](#url-extension)\n - [UserDefaults](#userdefaults-extension)\n\n\n**SwiftUI:**\n\n- [UIElementPreview](#uielementpreview)\n\n**SwiftUI Extension:**\n\n- [BindingExtension](#binding-extension)\n\n**UIKit Extensions:**\n\n- [UIAlertController](#uialertcontroller-extension)\n- [UIApplication](#uiapplication-extension)\n- [UIButton](#uibutton-extension)\n- [UICollectionView](#uicollectionview-extension)\n- [UICollectionViewCell](#uicollectionviewcell-extension)\n- [UIDevice](#uidevice-extension)\n- [UIFont](#uifont-extension)\n- [UIImage](#uiimage-extension)\n- [UILabel](#uilabel-extension)\n- [UIScreen](#uiscreen-extension)\n- [UISlider](#uislider-extension)\n- [UIStoryboard](#uistoryboard-extension)\n- [UISwitch](#uiswitch-extension)\n- [UITableView](#uitableview-extension)\n- [UITextField](#uitextfield-extension)\n- [UITextView](#uitextview-extension)\n- [UIView](#uiview-extension)\n- [UIViewController](#uiviewcontroller-extension)\n\n**UIKit Protocols:**\n\n- [NibLoadable](#nibloadable)\n- [NibOwnerLoadable](#nibownerloadable)\n\n**AppKit Extensions:**\n\n- [NSView](#nsview-extension)\n\n\n**Protocols:**\n\n- [Injectable](#injectable)\n- [Occupiable](#occupiable)\n- [Then](#then)\n\n**PropertyWrappers:**\n\n - [UserDefaultsBacked](#userdefaultsbacked)\n\n**Others:**\n\n- [UnitTesting](#unittesting)\n- [UITesting](#uitesting)\n- [SystemUtility - Shell](#shell-utility)\n\n## Swift, Foundation and CoreGraphics Extensions\n\n### Array extension\n\nSafely access to an element:\n\n```swift\nvar array = [1, 2, 3]\nprint(array[safe: 0]) // Optional(1)\nprint(array[safe: 10]) // nil\n```\n\nFind all the index of an object:\n\n```swift\nvar array = [1, 2, 3, 1]\nprint(array.indexes(of: 1)) // [0,3]\n```\n\nGet index of first / last occurrence of an object:\n\n``` swift\nvar array = [1, 2, 3, 1]\nprint(array.firstIndex(of: 1)) // Optional(0)\nprint(array.lastIndex(of: 1)) // Optional(3)\n```\n\nRemove an object:\n\n``` swift\nvar array = [1, 2, 3]\nmyArray.remove(object: 2)\nprint(myArray) // [1, 3]\nmyArray.remove(objects: [1, 3])\nprint(myArray) // []\n```\n\nRemove all the duplicates:\n\n```swift\nvar array = [0, 0, 1, 1, 2]\narray.removeDuplicates()\nprint(array) // [0,1,2]\n\nlet array = [0, 0, 1, 1, 2]\nlet newArray.removedDuplicates()\nprint(newArray) // [0,1,2]\n```\n\nRemove all instances of an item:\n\n``` swift\nvar array = [0, 0, 1, 1, 2]\narray.removeAll(0)\nprint(array) // [1,1,2]\n\nlet array = [0, 0, 1, 1, 2]\nlet newArray = array.removedAll(0)\nprint(newArray) // [1,1,2]\n```\n\nCheck if an array is a subset of another array:\n\n``` swift\nvar array = [1, 2, 3]\nprint(array.contains([1, 2])) // true\nprint(array.contains([5])) // false\n```\n\nDetermine if an array contains an object:\n\n``` swift\nvar array = [1, 2, 3]\nprint(array.contains(1)) // true\nprint(array.contains(11)) // false\n```\n\nGet intersection and union of two arrays:\n\n``` swift\nvar myArray = [1, 2, 3]\nprint(array.intersection(for: [1, 5, 3])) // [1, 3]\nprint(array.union(values: [5, 6])) // [1, 2, 3, 5, 6]\n```\n\nGet difference between two arrays:\n\n``` swift\nvar array = [1, 2, 3]\nprint(array.difference(with: [1])) // [2, 3]\n```\n\nSplit into chunk of a specific size:\n\n``` swift\nvar array = [1, 2, 3, 4]\nprint(array.split(intoChunksOf: 2)) // [[1, 2], [3, 4]]\n```\n\n### Bundle extension\n\nGet bundle information:\n\n```swift\nBundle.main.appName\nBundle(url: url)?.appName\n\nBundle.main.displayName\nBundle(url: url)?.displayName\n\nBundle.main.appVersion\nBundle(url: url)?.appVersion\n\nBundle.main.appBuild\nBundle(url: url)?.appBuild\n\nBundle.main.bundleId\nBundle(url: url)?.bundleId\n\nBundle.main.schemes\nBundle(url: url)?.schemes\n\nBundle.main.mainScheme\nBundle(url: url)?.mainScheme\n\nBundle.main.isInTestFlight\nBundle(url: url)?.isInTestFlight\n```\n\n### CGFloat extension\n\nCreate a CGFloat from a Float or an Integer:\n\n```swift\nlet imageViewTop = 15.f\n```\n\n### CGPoint extension\n\nAdd two `CGPoint`:\n\n```swift\nvar point1 = CGPoint(x: 10, y: 10)\nlet point2 = CGPoint(x: 10, y: 10)\nprint(point1 + point2) // CGPoint(x: 20, y: 20)\n\npoint1 += point2\nprint(point1) // CGPoint(x: 20, y: 20)\n```\n\nSubstract two `CGPoint`:\n\n```swift\nvar point1 = CGPoint(x: 10, y: 10)\nlet point2 = CGPoint(x: 10, y: 10)\nprint(point1 - point2) // CGPoint(x: 0, y: 0)\n\npoint1 -= point2\nprint(point1) // CGPoint(x: 0, y: 0)\n```\n\nMultiply a `CGPoint` with a scalar:\n\n```swift\nvar point1 = CGPoint(x: 10, y: 10)\nprint(point1 * 2) // CGPoint(x: 20, y: 20)\n\npoint1 *= 2\nprint(point1) // CGPoint(x: 20, y: 20)\n```\n\n### CGRect extension\n\nGet the origin's x and y coordinates:\n\n```swift\naRect.x // instead of aRect.origin.x\naRect.y // instead of aRect.origin.y\n```\n\nChange one property of a `CGRect`:\n\n```swift\nlet rect = CGRect(x: 10, y: 20, width: 30, height: 40) \nlet widerRect = rect.with(width: 100) // x: 10, y: 20, width: 100, height: 40\nlet tallerRect = rect.with(height: 100) // x: 10, y: 20, width: 30, height: 100\nlet rectAtAnotherPosition = rect.with(x: 100).with(y: 200) // x: 100, y: 200, width: 30, height: 40\nlet rectWithAnotherSize = rect.with(size: CGSize(width: 200, height: 200)) // x: 10, y: 20, width: 200, height: 200\nlet rectAtYetAnotherPosition = rect.with(origin: CGPoint(x: 100, y: 100)) // x: 100, y: 100, width: 30, height: 40\n```\n\n### CGSize extension\n\nAdd two `CGSize`:\n\n```swift\nvar size1 = CGSize(width: 10, height: 10)\nlet size2 = CGSize(width: 10, height: 10)\nprint(size1 + size2) // CGSize(width: 20, height: 20)\n\nsize1 += size2\nprint(size1) // CGSize(width: 20, height: 20)\n```\n\nSubstract two `CGSize`:\n\n```swift\nvar size1 = CGSize(width: 10, height: 10)\nlet size2 = CGSize(width: 10, height: 10)\nprint(size1 - size2) // CGSize(width: 0, height: 0)\n\nsize1 -= size2\nprint(size1) // CGSize(width: 0, height: 0)\n```\n\nMultiply a `CGSize` with a scalar:\n\n```swift\nvar size1 = CGSize(x: 10, y: 10)\nprint(size1 * 2) // CGSize(width: 20, height: 20)\n\nsize1 *= 2\nprint(size1) // CGSize(width: 20, height: 20)\n```\n\n### Color extension\n\nCreate colors with HEX values:\n\n```swift\nlet myUIColor = UIColor(hex: \"233C64\") // Equals 35,60,100,1\nlet myNSColor = NSColor(hex: \"233C64\") // Equals 35,60,100,1\n```\n\nAccess to individual color value:\n\n```swift\nlet myColor = UIColor(red: 120, green: 205, blue: 44, alpha: 0.3)\nprint(myColor.redComponent) // 120\nprint(myColor.greenComponent) // 205\nprint(myColor.blueComponent) // 44\nprint(myColor.alpha) // 0.3\n```\n\nGet lighter or darker variants of colors instances:\n\n```swift\nlet color = UIColor(red: 0.5, green: 0.5, blue: 1.0, alpha: 1.0)\nlet lighter = color.lighter(amount: 0.5)\nlet darker = color.darker(amount: 0.5)\n// OR\nlet lighter = color.lighter()\nlet darker = color.darker()\n\nlet color = NSColor(red: 0.5, green: 0.5, blue: 1.0, alpha: 1.0)\nlet lighter = color.lighter(amount: 0.5)\nlet lighter = color.lighter()\n// OR\nlet darker = color.darker(amount: 0.5)\nlet darker = color.darker()\n```\n\n### Data Extension\n\nInitialize from hex string:\n\n```swift\nlet hexString = \"6261736520313020697320736F2062617369632E206261736520313620697320776865726520697427732061742C20796F2E\"\nlet data = Data(hexString: hexString)\n```\n\nGet hex string from data:\n\n```swift\nlet data = Data(...)\nlet string = data.toHexString()\n// string = \"6261736520313020697320736F2062617369632E206261736520313620697320776865726520697427732061742C20796F2E\" if using previous example value\n```\n\nGet UInt8 Array from data:\n\n```swift\nlet data = Data(...)\nlet array = data.bytesArray\n```\n\nMap Data to Dictionary:\n\n```swift\nlet dictionary = try data.toDictionary()\n```\n\n### Date extension\n\nInitialize from string:\n\n```swift\nlet format = \"yyyy/MM/dd\"\nlet string = \"2015/03/11\"\nprint(Date(fromString: string, format: format)) // Optional(\"2015/03/11 00:00:00 +0000\")\n```\n\nConvert date to string:\n\n```swift\nlet now = Date()\nprint(now.string())\nprint(now.string(dateStyle: .medium, timeStyle: .medium))\nprint(now.string(format: \"yyyy/MM/dd HH:mm:ss\"))\n```\n\nSee how much time passed:\n\n```swift\nlet now = Date()\nlet later = Date(timeIntervalSinceNow: -100000)\nprint(later.days(since: now)) // 1.15740740782409\nprint(later.hours(since: now)) // 27.7777777733571\nprint(later.minutes(since: now)) // 1666.66666641732\nprint(later.seconds(since: now)) // 99999.999984026\n```\n\nCheck if a date is in future or past:\n\n```swift\nlet later = Date(timeIntervalSinceNow: -100000)\nprint(now.isInFuture) // false\nprint(now.isInPast) // true\n```\n\n### Dictionary extension\n\nCheck if a key exists in the dictionary:\n\n```swift\nlet dic = [\"one\": 1, \"two\": 2]\nprint(dic.has(key: \"one\")) // True\nprint(dic.has(key: \"1\")) // False\n```\n\nMap Dictionary to Data:\n\n```swift\nlet data = try dictionary.toData()\n```\n\nEasily get union of two dictionaries:\n\n```swift\nlet dic1 = [\"one\": 1, \"two\": 2]\nlet dic2 = [\"one\": 1, \"four\": 4]\n\nlet dic3 = dic1.union(values: dic2)\nprint(dic3) // [\"one\": 1, \"two\": 2, \"four\": 4]\n```\n\n`map` a dictionary:\n\n```swift\nlet dic = [\"a\": 1, \"b\": 2, \"c\": 3]\nlet result = dic.map { key, value in\n\treturn (key.uppercased(), \"\\(value * 2)\")\n}\nprint(dic) // [\"A\": \"2, \"B\": \"4\", \"C\": \"6\"]\n```\n\n`flatMap` a dictionary:\n\n```swift\nlet dic = [\"a\": 1, \"b\": 2, \"c\": 3]\nlet result = dic.flatMap { key, value -\u003e (String, String)? in\n\tif value % 2 == 0 {\n\t \treturn nil\n\t}\n\treturn (key.uppercased(), \"\\(value * 2)\")\n}\nprint(dic) // [\"A\": \"2, \"C\": \"6\"]\n```\n\nGet difference of two dictionaries:\n\n```swift\nlet dic1 = [\"one\": 1, \"two\": 2]\nlet dic2 = [\"one\": 1, \"four\": 4]\ndifference(with: dic1, dic2) // [\"two\": 2, \"four\": 4]\n```\n\nMerge several dictionaries:\n\n```swift\nlet dic1 = [\"one\": 1, \"two\": 2]\nlet dic2 = [\"three\": 3, \"four\": 4]\nvar finalDic = [String: Int]()\nfinalDic.merge(with: dic1, dic2)\nprint(finalDic) // [\"one\": 1, \"two\": 2, \"three\": 3, \"four\": 4]\n```\n\n### Double extension\n\nGet the time interval for a number of milliseconds, seconds, hour, or days:\n\n```swift\nprint(1.second) // 1\nprint(1.minute) // 60\nprint(1.hour) // 3600\nprint(1.2.seconds) // 1.2\nprint(1.5.minutes) // 90.0\nprint(1.5.hours) // 5400.0\nprint(1.3.milliseconds) // 0.0013\nprint(0.5.day) // 43200\nprint(1.day) // 86400\nprint(2.day) // 172800\n```\n\nFormatted value with the locale currency:\n\n```swift\nprint(Double(3.24).formattedPrice) // \"$3.24\"\nprint(Double(10).formattedPrice) // \"$10.00\"\n```\n\n\n### FileManager extension\n\nGet documents directory url following the os:\n\n```swift\nFileManager.document\n// OR\nFileManager.default.document\n```\n\nCreate a new directory:\n\n```swift\nFileManager.createDirectory(at: directoryUrl)\n// OR\nFileManager.default.createDirectory(at: directoryUrl)\n```\n\nDelete contents of temporary directory\n\n```swift\nFileManager.removeTemporaryFiles()\n// OR\nFileManager.default.removeTemporaryFiles()\n```\n\nDelete contents of documents directory\n\n```swift\nFileManager.removeDocumentFiles()\n// OR\nFileManager.default.removeDocumentFiles()\n```\n\n### Int extension\n\n```swift\nvar myNumber = -33\nprint(myNumber.isEven) // false\nprint(myNumber.isOdd) // true\nprint(myNumber.isPositive) // false\nprint(myNumber.isNegative) // true\nprint(myNumber.digits) // 2\n```\n\nRound to the nearest / nearest down / nearest up:\n\n```swift\nvar value = 17572\nprint(value.nearestDozens) // 17570\nprint(value.nearestHundreds) // 17600\nprint(value.nearestThousands) // 18000\nprint(value.nearest(to: 1000) // 18000\n\nvalue = 17578\nprint(value.nearestBelowDozens) // 17570\nprint(value.nearestBelowHundreds) // 17500\nprint(value.nearestBelowThousands) // 17000\nprint(value.nearestBelow(to: 1000) // 17000\n\nvalue = 17442\nprint(value.nearestUpDozens) // 17450\nprint(value.nearestUpHundreds) // 17500)\nprint(value.nearestUpThousands) // 18000\nprint(value.nearestUp(to: 1000) // 18000\n```\n\nFormatted value with the locale currency:\n\n```\nprint(10.formattedPrice) // \"$10.00\"\n```\n\n### MutableCollection extension\n\nSorts the mutable collection in place using `KeyPath`:\n\n```swift\nvar articles = [Article(title: \"B\"), Article(title: \"C\"), Article(title: \"A\")]\narticles.sort(by: \\.title) // [A, B, C]\narticles.sort(by: \\.title, order: \u003e) // [C, B, A]\n```\n\n### NotificationCenter extension\n\nPost a notification from a specific queue:\n\n```swift\nNotificationCenter.default.postNotification(\"aNotification\", queue: DispatchQueue.main) \nNotificationCenter.default.postNotification(\"aNotification\", object: aObject queue: DispatchQueue.main)\nNotificationCenter.default.postNotification(\"aNotification\", object: aObject userInfo: userInfo queue: DispatchQueue.main)\n```\n\n### NSAttributedString extension\n\nCheck if an attribute is applied on the desired substring:\n\n```swift\nlet text = \"Hello\"\nlet attrString = NSMutableAttributedString(text: \"Hello world\")\nattrString = attrString.underlined(occurences: text)\nattrString.isAttributeActivated(.underlineStyle, appliedOn: text, value: 1) // true\n```\n\n### NSLayoutConstraint extension\n\n*No available for watchOS*\n\nApply a multiplier to a constraint (currently working only for width and height):\n\n```swift\nlet view = UIView(CGRect(x: 0, y: 0, width: 100, height: 200))\nlet constraint = NSLayoutConstraint(item: view, attribute: .width, ...)\nconstraint.apply(multiplier: 0.5, toView: superview)\nprint(constraint.constants) // 50\n\nlet constraint = NSLayoutConstraint(item: view, attribute: .height, ...)\nconstraint.apply(multiplier0.5, toView: superview)\nprint(constraint.constants) // 100\n```\n\n\n### NSMutableAttributedString extension\n\nColorize each occurence:\n\n```swift\nlet attrStr: NSMutableAttributedString = NSMutableAttributedString.colored(inText: \"hello world\", color: .yellow, occurences: \"llo\")\n\n// OR\n\nlet attrStr: NSMutableAttributedString = NSMutableAttributedString(string: \"Hello world\")\nattrStr.color(.yellow, occurences: \"llo\")\n```\n\nColorize everything after an occurence:\n\n```swift\nlet attrStr = NSMutableAttributedString.colored(inText: \"Hello world\", color: .yellow, afterOcurrence: \"llo\")\n\n// OR\n\nlet attrStr = NSMutableAttributedString(string: \"Hello world\")\nattrStr.color(.yellow, afterOcurrence: \"llo\")\n```\n\nStrike each occurence:\n\n```swift\nlet attrStr: NSMutableAttributedString = NSMutableAttributedString.struck(inText: \"Hello world\", occurences: \"llo\")\n\n// OR\n\nlet attrStr = NSMutableAttributedString(string: \"Hello world\")\nattrStr.strike(occurences: \"llo\")\n```\n\nStrike everything after an occurence:\n\n```swift\nlet attrStr: NSMutableAttributedString = NSMutableAttributedString.struck(inText: \"Hello world\", afterOcurrence: \"llo\")\n\n// OR\n\nlet attrStr = NSMutableAttributedString(string: \"Hello world\")\nattrStr.strike(ocurrences: \"llo\")\n```\n\nUnderline each occurence:\n\n```swift\nlet attrStr: NSMutableAttributedString = NSMutableAttributedString.underlined(inText: \"Hello world\", occurences: \"llo\")\n\n// OR\n\nlet attrStr = NSMutableAttributedString(string: \"Hello world\")\nattrStr.underline(occurences: \"llo\")\n```\n\nUnderline everything after an occurence:\n\n```swift\nlet attrStr: NSMutableAttributedString = NSMutableAttributedString.underlined(inText: \"Hello world\", afterOcurrence: \"llo\")\n\n// OR\n\nlet attrStr = NSMutableAttributedString(string: \"Hello world\")\nattrStr.underline(afterOcurrence: \"llo\")\n```\n\nUse custom font for each occurence:\n\n```swift\nlet font = UIFont.boldSystemFont(ofSize: 15)\nlet attrStr: NSMutableAttributedString = NSMutableAttributedString.font(inText: \"hello world\", font: font, occurences: \"llo\")\n\n// OR\n\nlet attrStr: NSMutableAttributedString = NSMutableAttributedString(string: \"Hello world\")\nattrStr.font(font, occurences: \"llo\")\n```\n\nCustom font for everything after an occurence:\n\n```swift\nlet font = UIFont.boldSystemFont(ofSize: 15)\nlet attrStr = NSMutableAttributedString.colored(inText: \"Hello world\", font: font, afterOcurrence: \"llo\")\n\n// OR\n\nlet attrStr = NSMutableAttributedString(string: \"Hello world\")\nattrStr.font(font, afterOcurrence: \"llo\")\n```\n\n### NSObject extension\n\nGet the class name of a `NSObject`:\n\n```swift\n#if !os(macOS)\n\tlet vc = NSViewController()\n\tprint(vc.className) // NSViewController\n#else\n\tlet vc = UIViewController()\n\tprint(vc.className) // UIViewController\n\tprint(UIViewController.className) // UIViewController\n#endif\n```\n\n### NSRange extension\n\nRange after an occurence:\n\n```swift\nlet string = \"Hello world\"\nlet range = NSRange(text: string, afterOccurence: \"llo\")\nprint(range) // location: 3, length: 8\n```\n\nRange of string:\n\n```swift\nlet string = \"Hello world\"\nlet stringToFind = \"ello wo\"\nlet range = NSRange(textToFind: stringToFind, in: string)\nprint(range) // location: 1, length: 7\n```\n\n### ReusableFormatters\n\nReuse your formatter to avoid heavy allocation:\n\n```swift\nSUDateFormatter.shared\nSUNumberFormatter.shared\nSUByteCountFormatter.shared\nSUDateComponentsFormatter.shared\nSUDateIntervalFormatter.shared\nSUEnergyFormatter.shared\nSUMassFormatter.shared\n```\n\n### Sequence extension\n\nSort a sequence using `keyPath`:\n\n```swift\nlet articles = [Article(title: \"B\"), Article(title: \"C\"), Article(title: \"A\")]\nvar sortedArticles = articles.sorted(by: \\.title) // [A, B, C]\nsortedArticles = articles.sorted(by: \\.title, order: \u003e) // [C, B, A]\n```\n\n### String extension\n\nAccess with subscript:\n\n```swift\nvar string = \"hello world\"\nprint(string[0]) // h\nprint(string[2]) // l\nprint(string[Range(1...3)]) // ell\n```\n\nCheck if it contains a string:\n\n```swift\nlet string = \"Hello world\"\nprint (string.contains(text: \"hello\")) // true\nprint (string.contains(text: \"hellooooo\")) // false\n```\n\nCheck if it's a number:\n\n```swift\nvar string = \"4242\"\nprint(string.isNumber) // true\n\nvar string = \"test\"\nprint(string.isNumber) // false\n```\n\nCheck if it's a valid email:\n\n```swift\n// (deprecated)\nvar string = \"test@gmail.com\"\nprint(string.isEmail) // true\nvar string = \"test@\"\nprint(string.isEmail) // false\n```\n\n```swift\n// current\nvar support = try \"test@gmail.com\".validateEmailAddress() // EmailSupport.widelySupported\nstring = try \"test+tag@gmail.com\".validateEmailAddress() // EmailSupport.mostlySupported\nstring = try \"\\\"abc@def\\\"@gmail.com\".validateEmailAddress() // EmailSupport.technicallySupported\nstring = try \"test@\".validateEmailAddress() // throws an error for lack of a domain\n```\n\nCheck it's a valid domain:\n```swift\ntry \"google.com\".validateDomain() // doesn't throw\ntry \"google..com\".validateDomain() // throws because of sequential dots in value\n```\n\nCheck if it's a valid IP address:\n\n```swift\nlet ip4 = \"1.2.3.4\"\nlet ip6 = \"fc00::\"\nlet notIPAtAll = \"i'll bribe you to say i'm an ip address!\"\n\nip4.isIP4Address //true\nip4.isIP6Address //false\nip4.isIPAddress //true\n\nip6.isIP4Address //false\nip6.isIP6Address //true\nip6.isIPAddress //true\n\nnotIPAtAll.isIP4Address //false\nnotIPAtAll.isIP6Address //false\nnotIPAtAll.isIPAddress //false\n```\n\nUncamelize a string:\n\n```swift\nvar camelString = \"isCamelled\"\nprint(camelString.uncamelize) // is_camelled\n```\n\nCapitalize the first letter:\n\n```swift\nvar string = \"hello world\"\nstring = string.capitalizedFirst\nprint(string)// Hello world\n```\n\nTrimmed spaces and new lines:\n\n```swift\nvar string = \" I'  am a    test  \\n  \"\nprint(string.trimmed()) // I'am a test\n```\n\nTruncated to have a limit of characters:\n\n```swift\nvar string = \"0123456789aaaa\"\nprint(string.truncate(limit: 10)) // 0123456789...\n```\n\nSplit string in chunks of n elements:\n\n```swift\nlet string = \"abcd\"\nprint(string.split(intoChunksOf: 2)) // [\"ab\", \"cd\"]\n```\n\n### Timer extension\n\nSchedule timer every seconds:\n\n```swift\nvar count = 0\nTimer.every(1.second, fireImmediately: true) { timer in // fireImmediately is an optional parameter, defaults to false\n    print(\"Will print every second\")\n    if count == 3 {\n        timer.invalidate()\n    }\n    count++\n}\n```\n\nSchedule timer after a certain delay:\n\n```swift\nTimer.after(2.seconds) { _ in\n    print(\"Prints this 2 seconds later in main queue\")\n}\n```\n\nManual scheduling a timer:\n\n```swift\nlet timer = Timer.new(every: 2.seconds) { _ in\n    print(\"Prints this 2 seconds later in main queue\")\n}\ntimer.start(onRunLoop: RunLoop.current, modes: RunLoopMode.defaultRunLoopMode)\n```\n\nManual scheduling a timer with a delay:\n\n```swift\nlet timer = Timer.new(after: 2.seconds) { _ in\n    print(\"Prints this 2 seconds later in main queue\")\n}\ntimer.start(onRunLoop: RunLoop.current, modes: RunLoopMode.defaultRunLoopMode)\n```\n\n### URL extension\n\nGet query parameters from URL:\n\n```swift\nlet url = URL(string: \"http://example.com/api?v=1.1\u0026q=google\")\nlet queryParameters = url?.queryParameters\nprint(queryParameters?[\"v\"]) // 1.1\nprint(queryParameters?[\"q\"]) // google\nprint(queryParameters?[\"other\"]) // nil\n```\n\nAdd skip backup attributes to you URL:\n\n```swift\nlet url = URL(string: \"/path/to/your/file\")        \nurl?.addSkipBackupAttribute() // File at url won't be backupped!\n```\n\n### UserDefaults extension\n\nGet and set values from `UserDefaults` with subscripts:\n\n```swift\nlet Defaults = UserDefaults.standard\nDefaults[\"userName\"] = \"test\"\nprint(Defaults[\"userName\"]) // test\n```\n\nCheck if the `UserDefaults` has a key:\n\n```swift\nUserDefaults.has(key: \"aKey\")\n// OR\nUserDefaults.standard.has(key: \"aKey\")\n```\n\nRemove all values in `UserDefaults`:\n\n```swift\nUserDefaults.standard.removeAll()\n```\n\n\n## SwiftUI\n\n### UIElementPreview\n\nGenerate automatically multiple previews including: \n\n- Default sized preview or dedicated preview device\n- A preview with Dark Mode enabled\n- Each localization of our project applied to a preview\n- Different dynamic type sizes applied\n\n\n```swift\nstruct ContentView_Previews: PreviewProvider {\n    static var previews: some View {\n        UIElementPreview(ContentView(),\n                         previewLayout: .sizeThatFits, // default is `.device`\n                         previewDevices: [\"iPhone SE\"], // default is iPhone SE and iPhone XS Max. Note: it won't be used if `previewLayout` is `.sizeThatFits`\n                         dynamicTypeSizes:[.extraSmall] // default is: .extraSmall, .large, .extraExtraExtraLarge\n                        )\n    }\n}\n```\n\n## SwiftUI Extensions\n\n### Binding extension\n\nPass an interactive value that’ll act as a preview stand-in for a binding:\n\n```swift\nstruct MyButton: View {\n    @Binding var isSelected: Bool\n    // ...\n}\n\nstruct MyButton_Previews: PreviewProvider {\n    static var previews: some View {\n        MyButton(isSelected: .mock(true))\n    }\n}\n```\n\n## UIKit Extensions\n\n### UIAlertController extension\n\nCreate a custom `UIAlertController`:\n\n```swift\nlet alertController1 = UIAlertController(title: \"Title\",\n                                        message: \"Message\")\n                          \nlet alertController2 = UIAlertController(title: \"Title\",\n                                        message: \"Message\",\n                                        defaultActionButtonTitle: \"Cancel\")\n                                                      \nlet alertController3 = UIAlertController(title: \"Title\",\n                                        message: \"Message\",\n                                        defaultActionButtonTitle: \"Cancel\",\n                                        defaultActionButtonStyle: .cancel) \n                                        \nlet alertController1 = UIAlertController(title: \"Title\",\n                                        message: \"Message\",\n                                        defaultActionButtonTitle: \"Cancel\",\n                                        defaultActionButtonStyle: .cancel,\n                                        tintColor: .blue)\n```\n\nShow an `UIAlertController`:\n\n```swift\nalertController.show()\nalertController.show(animated: false)\nalertController.show(animated: true, completion: {\n    print(\"Presented\")\n})\n```\n\nAdd an action to the `UIAlertController`:\n\n```swift\nalertController.addAction(title: \"ActionTitle\")\n\nalertController.addAction(title: \"ActionTitle\",\n                          style: .destructive)\n                          \nalertController.addAction(title: \"ActionTitle\",\n                          style: .destructive,\n                          isEnabled: false)\n                          \nalertController.addAction(title: \"ActionTitle\",\n                          style: .destructive,\n                          isEnabled: false,\n                          handler: nil)\n```\n\n### UIApplication extension\n\nGet the current view controller display:\n\n```swift\nUIApplication.shared.topViewController() // Using UIWindow's rootViewController as baseVC\nUIApplication.shared.topViewController(from: baseVC) // topVC from the base view controller\n```\n\nGet the app delegate:\n\n```swift\nUIApplication.delegate(AppDelegate.self)\n```\n\nOpen app settings:\n\n```swift\nUIApplication.shared.openAppSettings()\n```\n\nOpen app review page:\n\n```swift\nlet url = URL(string: \"https://itunes.apple.com/app/{APP_ID}?action=write-review\")\nUIApplication.shared.openAppStoreReviewPage(url)\n```\n\n### UIButton extension\n\nAdd right image with custom offset to button:\n\n```swift\nlet button = UIButton(frame: .zero)\nbutton.addRightImage(image, offset: 16)\n```\n\n### UICollectionView extension\n\nRegister and dequeue safely your `UICollectionViewCell`:\n\n```swift\n// 1. Make your `UICollectionCell` conforms to `Reusable` (class-based) or `NibReusable` (nib-based)\nfinal class ReusableClassCollectionViewCell: UICollectionViewCell, Reusable {}\n// 2. Register your cell:\ncollectionView.register(cellType: ReusableClassCollectionViewCell.self)\n// 3. Dequeue your cell:\nlet cell: ReusableClassCollectionViewCell = collectionView.dequeueReusableCell(at: indexPath)\n```\n\nRegister and dequeue safely your `UICollectionReusableView`:\n\n```swift\n// 1. Make your `UICollectionReusableView` conforms to `Reusable` (class-based) or `NibReusable` (nib-based)\nfinal class ReusableNibCollectionReusableView: UICollectionReusableView, NibReusable\n// 2. Register your cell:\ncollectionView.register(supplementaryViewType: ReusableNibCollectionReusableView.self, ofKind: UICollectionView.elementKindSectionHeader)\n// 3. Dequeue your cell:\nlet header: ReusableNibCollectionReusableView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: indexPath)\n```\n\n### UICollectionViewCell extension\n\nApply a corner radius to the cell:\n\n```swift\nlet cell = UICollectionViewCell()\ncell.applyCornerRadius(10)\n```\n\nAnimate when cell is highlighted:\n\n```swift\nclass MyCollectionViewCell: UICollectionViewCell {\n    // ...\n    override var isHighlighted: Bool {\n        willSet {\n            self.animate(scale: newValue, options: .curveEaseInOut) // Note that the animation is customisable, but all parameters as default value\n        }\n    }\n    // ...\n}\n```\n\n### UIFont extension\n\nObtains a font that scale to support Dynamic Type:\n\n```swift\nlet font = UIFont.dynamicStyle(.body, traits: .traitsBold)\n```\n\n### UIDevice extension\n\nAccess to your device information:\n\n```swift\nprint(UIDevice.idForVendor) // 104C9F7F-7403-4B3E-B6A2-C222C82074FF\nprint(UIDevice.systemName()) // iPhone OS\nprint(UIDevice.systemVersion()) // 9.0\nprint(UIDevice.deviceName) // iPhone Simulator / iPhone 6 Wifi\nprint(UIDevice.deviceLanguage) // en\nprint(UIDevice.isPhone) // true or false\nprint(UIDevice.isPad) // true or false\n```\n\nCheck your system version:\n\n```swift\nprint(UIDevice.isVersion(8.1)) // false\nprint(UIDevice.isVersionOrLater(8.1)) // true\nprint(UIDevice.isVersionOrEarlier(8.1)) // false\n```\n\nForce device orientation:\n\n```swift\nUIDevice.forceRotation(.portrait)\nUIDevice.current.forceRotation(.portrait)\n```\n\n### UIImage extension\n\nCreate an image from a color:\n\n```swift\nlet image = UIImage(color: .green)\n```\n\nFill an image with a color:\n\n```swift\nlet image = UIImage(named: \"image\")\nlet greenImage = image.filled(with: .green)\n```\n\nCombined an image with another:\n\n```swift\nlet image = UIImage(named: \"image\")\nlet image2 = UIImage(named: \"image2\")\nlet combinedImage = image.combined(with: image2)\n```\n\nChange the rendering mode:\n\n```swift\nvar image = UIImage(named: \"image\")\nimage = image.template // imageWithRenderingMode(.alwaysTemplate)\nimage = image.original // imageWithRenderingMode(.alwaysOriginal)\n```\n\n### UILabel extension\n\nConfigure a dynamic text style to the label:\n\n```swift\nlabel.configureDynamicStyle(.body, traits: .traitBold)\n```\n\nDetect if a label text is truncated:\n\n```swift\nlet label = UILabel(frame: CGRect(x: 0, y: 0, width: 30, height: 40))\nlabel.text = \"I will be truncated :(\"\nprint(label.isTruncated()) // true\n\nlet label = UILabel(frame: CGRect(x: 0, y: 0, width: 30, height: 40))\nlabel.text = \":)\"\nprint(label.isTruncated()) // false\n```\n\nCustomize label line height:\n\n```swift\nlet label = UILabel(frame: CGRect(x: 0, y: 0, width: 30, height: 40))\nlabel.setText(\"A long multiline text\")\nlabel.setLineHeight(0.9)\n```\n\nCustomize the label truncated text (replace the default `...`):\n\n```swift\nlet label = UILabel(frame: CGRect(x: 0, y: 0, width: 30, height: 40))\nlabel.setText(\"I will be truncated :(\", truncatedText: \".\")\nprint(label.text) // I wi.\n```\n\n### UIScreen extension\n\nGet the screen orientation:\n\n```swift\nif UIInterfaceOrientationIsPortrait(UIScreen.currentOrientation) {\n    // Portrait\n} else {\n    // Landscape\n}\n```\n\nGet the screen size:\n\n```swift\nprint(UIScreen.size) // CGSize(375.0, 667.0) on iPhone6\nprint(UIScreen.width) // 375.0 on iPhone6\nprint(UIScreen.height) // 667.0 on iPhone6\nprint(UIScreen.heightWithoutStatusBar) // 647.0 on iPhone6\n```\n\nGet the status bar height:\n\n```swift\nprint(UIScreen.statusBarHeight) // 20.0 on iPhone6\n```\n\n### UISlider extension\n\nGet the value where the user tapped using an `UITapGestureRecognizer`:\n\n```\nlet slider = UISlider(frame: .zero)\nslider.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(sliderTapped(_:))))\n\nfunc sliderTapped(sender: UITapGestureRecognizer) {\n    let value = slider.value(for: sender)\n}\n```\n\n### UIStoryboard extension\n\nGet the application's main storyboard:\n\n```swift\nlet storyboard = UIStoryboard.main\n```\n\n### UISwitch extension\n\nToggle `UISwitch`:\n\n```swift\nlet aSwitch = UISwitch(frame: CGRect(x: 0, y: 0, width: 100, height: 30))\naSwitch.toggle()\nprint(aSwitch.isOn) // true\n\naSwitch.toggle(animated: false)\n```\n\n### UITableView\n\nRegister and dequeue safely your `UITableViewCell`:\n\n```swift\n// 1. Make your `UITableViewCell` conforms to `Reusable` (class-based) or `NibReusable` (nib-based)\nfinal class ReusableClassTableViewCell: UITableViewCell, Reusable {}\n// 2. Register your cell:\ntableView.register(cellType: ReusableClassTableViewCell.self)\n// 3. Dequeue your cell:\nlet cell: ReusableClassTableViewCell = tableView.dequeueReusableCell(at: indexPath)\n```\n\nRegister and dequeue safely your `UITableViewHeaderFooterView`:\n\n```swift\n// 1. Make your `UITableViewHeaderFooterView` conforms to `Reusable` (class-based) or `NibReusable` (nib-based)\nfinal class ReusableClassHeaderFooterView: UITableViewHeaderFooterView, Reusable {}\n// 2. Register your header or footer:\ntableView.register(headerFooterViewType: ReusableClassHeaderFooterView.self)\n// 3. Dequeue your header or footer:\nlet cell: ReusableClassHeaderFooterView = tableView.dequeueReusableHeaderFooterView()\n```\n\n### UITextField extension\n\nConfigure a dynamic text style to the textfield:\n\n```swift\ntextField.configureDynamicStyle(.body, traits: .traitBold)\n```\n\nModify clear button image:\n\n```swift\nlet clearButtonImage = UIImage(named: \"clear_button\")\nlet textField = UITextField()\ntextField.setClearButton(with: clearButtonImage)\n```\n\nModify placeholder's color:\n\n```swift\nlet textField = UITextField()\n// set `placeholder` or `attributedPlaceholder`\ntextField.setPlaceHolderTextColor(.blue)\n```\n\n### UITextView extension\n\nConfigure a dynamic text style to the textfield:\n\n```swift\ntextView.configureDynamicStyle(.body, traits: .traitBold)\n```\n\n### UIView extension\n\nChange the frame of the view easily:\n\n```swift\nlet aView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))\naView.x += 100 // move  to right\naView.y += 100 // move downwards\naView.width -= 10 // make the view narrower\naView.height -= 10 // make the view shorter \n```\n\nApply a corner radius to the view:\n\n```swift\nlet view = UIView()\nview.applyCornerRadius(10)\nview.applyCornerRadius(20, maskedCorners: [.layerMaxXMaxYCorner])\n```\n\nFind the `ViewController` which contains this view:\n\n```swift\nlet parent: UIViewController? = aView.parentViewController\n```\n\nFind a subview using its `accessibilityIdentifier, useful to tests private outlets:\n\n```swift\naView.findView(forIdentifier: \"accessibilityIdentifier\")\n```\n\nFind the first subview corresponding to a  specific type:\n\n```swift\nlet scrollView: UIScrollView? = aView.findView()\n```\n\nAdd a SwiftUI `View` as a subview:\n\n```swift\naView.addSubSwiftUIView(SwiftUIView())\n```\n\nAutomates your localizables:\n\n```swift\naView.translateSubviews()\n```\n\nIt will iterate on all the subviews of the view, and use the text / placeholder as key in `NSLocalizedString`.\nBy settings your localizable key in your xib / storyboard, all yours string will be automatically translated just by calling the above method.\n\nAdd constraints between a view and its superview:\n\n```swift\naView.addConstraints() // Add constraints to all edges with zero insets\naView.addConstraints(to: [.top, .bottom]) // Add constraints to top and bottom edges with zero insets\naView.addConstraints(to: [.top, .left], insets: UIEdgeInsets(top: 10, left: 20, bottom: 0, right: 0)) // Add constraints to top and left edges with custom insets\n```\n\n### UIViewController extension\n\nGenerate a Xcode preview for any view controllers:\n\n```swift\n@available(iOS 13, *)\nstruct MyViewPreview: PreviewProvider {\n    static var previews: some View {\n        MyViewController().preview\n    }\n}\n```\n\nReset the navigation stack by deleting previous view controllers:\n\n```swift\nlet navController = UINavigationController()\nnavController.pushViewController(vc1, animated: true)\nnavController.pushViewController(vc2, animated: true)\nnavController.pushViewController(vc3, animated: true)\nvc3.removePreviousControllers(animated: true)\nprint(navController.viewControllers) // [vc3]\n```\n\nCheck if ViewController is onscreen and not hidden:\n\n```swift\nlet viewController = UIViewController()\nprint(viewController.isVisible) // false\n```\n\nCheck if ViewController is presented modally:\n\n```swift\nlet viewController = UIViewController()\nprint(viewController.isModal)\n```\n\nOpen Safari modally:\n\n```\nlet url = URL(string: \"https://www.apple.com\")\nvc.openSafariVC(url: url, delegate: self)\n```\n\nAdd a child view controller to another controller:\n\n```swift\nvc.addChildController(childVC, subview: vc.view, animated: true, duration: 0.35, options: [.curveEaseInOut, .transitionCrossDissolve])\n```\n\nAdd a child view controller to a container view:\n\n```swift\nvc.addChildController(childVC, in: containerView)\n```\n\nRemove a child view controller:\n\n```swift\nvc.removeChildController(childVC)\n```\n\nAdd a SwiftUI `View` as a child of the input `UIView`:\n\n```swift\nvc.addSubSwiftUIView(SwiftUIView(), to: vc.view)\n```\n\n## UIKit Protocols:\n\n### NibLoadable\n\nMake your `UIView` subclasses conform to this protocol to instantiate them from their NIB safely.\n**Note:** Be sure that your `UIView` is based on a Nib, and is used as the Xib's root view.\n\n```swift\nclass NibLoadableView: UIView, NibLoadable {\n    // ...\n}\n\nlet view = NibLoadableView.loadFromNib()\n```\n\n### NibOwnerLoadable\n\nMake your `UIView` subclasses conform to this protocol to instantiate them from their Xib's File Owner safely.\n**Note:** Be sure that your `UIView` is based on a Nib, and is used as the Xib's File's Owner.\n\n```swift\nclass NibLoadableView: UIView, NibOwnerLoadable {\n    // ...\n    \n    required init?(coder aDecoder: NSCoder) {\n      super.init(coder: aDecoder)\n      self.loadNibContent()\n    }\n\n}\n\n// Then use it directly from another xib or whatever...\n```\n\n## AppKit, Cocoa Extensions\n\n### NSView extension\n\nChange the frame of the view easily\n\n```swift\nlet aView = NSView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))\naView.x += 100 // move  to right\naView.y += 100 // move downwards\naView.width -= 10 // make the view narrower\naView.height -= 10 // make the view shorter \n```\n\n**Automates your localizables**\n\n```swift\naView.convertLocalizables()\n```\n\nIt will iterate on all the subviews of the view, and use the text / placeholder as key in `NSLocalizedString`.\nBy settings your localizable key in your xib / storyboard, all yours string will be automatically translated just by calling the above method.\n\n## Protocols\n\n### Injectable\n\nProtocol to do `ViewController` Data Injection with Storyboards and Segues in Swift. Inspired from [Nastasha's blog](https://www.natashatherobot.com/update-view-controller-data-injection-with-storyboards-and-segues-in-swift/):\n\n```swift\nclass RedPillViewController: UIViewController, Injectable {\n\n    @IBOutlet weak private var mainLabel: UILabel!\n\n    // the type matches the IOU's type\n    typealias T = String\n\n    // this is my original dependency (IOU)\n    // I can now make this private!\n    private var mainText: String!\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n\n        // this will crash if the IOU is not set\n        assertDependencies()\n\n        // using the IOU if needed here,\n        // but using it later is fine as well\n        mainLabel.text = mainText\n    }\n\n    // Injectable Implementation\n    func inject(text: T) {\n        mainText = text\n    }\n\n    func assertDependencies() {\n        assert(mainText != nil)\n    }\n}\n\n// ViewController that will inject data...\noverride func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {\n\n    switch segueIdentifierForSegue(segue) {\n    case .TheRedPillExperience\n        let redPillVC = segue.destinationViewController as? RedPillViewController\n        redPillVC?.inject(\"😈\")\n    case .TheBluePillExperience:\n        let bluePillVC = segue.destinationViewController as? BluePillViewController\n        bluePillVC?.inject(\"👼\")\n    }\n}\n```\n\n### Occupiable\n\nThe following use cases works for String Array, Dictionary, and Set\n\n`isEmpty` / `isNotEmpty`\n\n*No optional types only*\n\n```swift\nvar string = \"Hello world\"\nprint(string.isNotEmpty) // true\nprint(string.isEmpty) // false\n```\n\n`isNilOrEmpty`\n\n*Optional types only*\n\n```swift\nlet string: String? = \"\"\nprint(string.isNilOrEmpty) // true\n```\n\n### Then\n\nSyntactic sugar for Swift initializers:\n\n```swift\nlet label = UILabel().then {\n    $0.textAlignment = .Center\n    $0.textColor = .blackColor()\n    $0.text = \"Hello, World!\"\n}\n```\n\n## PropertyWrappers\n\n### UserDefaultsBacked\n\nType safe access to UserDefaults with support for default values.\n\n```swift\nstruct SettingsViewModel {\n    @UserDefaultsBacked(key: \"search-page-size\", defaultValue: 20)\n    var numberOfSearchResultsPerPage: Int\n\n    @UserDefaultsBacked(key: \"signature\")\n    var messageSignature: String?\n}\n```\n\n## Others\n\n### UnitTesting\n\nGrand Central Dispatch sugar syntax:\n\nDetect if UITests are running:\n\n```swift\nif UnitTesting.isRunning {\n  // tests are running\n} else {\n  // everything is fine, move along\n}\n```\n\nMeasure tests performance:\n\n```swift\nfunc testPerformance() {\n  let measurement = measure {\n    // run operation\n  }\n}\n```\n\n### UITesting\n\nDetect if UITests are running:\n\n```swift\nif UITesting.isRunning {\n  // tests are running\n} else {\n  // everything is fine, move along\n}\n```\n\n### Shell Utility \n(macOS only)\n\nRuns a command on a system shell and provides the return code for success, STDOUT, and STDERR.\n\nSTDOUT as one continuous String:\n\n```swift\nlet (rCode, stdOut, stdErr) = SystemUtility.shell([\"ls\", \"-l\", \"/\"])\n// rCode = 0 (which is \"true\" in shell)\n// stdOut = \"total 13\\ndrwxrwxr-x+ 91 root  admin  2912 Feb 11 01:24 Applications\" ...  etc\n// stdErr = [\"\"]\n```\n\nSTDOUT as array of Strings separated by newlines:\n\n```swift\nlet (rCode, stdOut, stdErr) = SystemUtility.shellArrayOut([\"ls\", \"-l\", \"/\"])\n// rCode = 0 (which is \"true\" in shell)\n// stdOut = [\"total 13\", \"drwxrwxr-x+ 91 root  admin  2912 Feb 11 01:24 Applications\" ...  etc]\n// stdErr = [\"\"]\n```\n\n\n## Installation\n\n- Xcode 8 and later\n- Swift 3.0\n- iOS 8.0 or later\n- macOS 10.10 or later\n- tvOS 9.0 or later\n- watchOS 2.0 or later\n\n### Manually\n\nCopy the SwiftyUtils folder into your Xcode project. (Make sure you add the files to your target(s))\n\n### CocoaPods\n\nAdd `pod SwiftyUtils` to your Podfile.\n\n### Carthage\n\nAdd `github \"tbaranes/SwiftyUtils\"` to your Cartfile.\n\n### Swift Package Manager\n\nYou can use [The Swift Package Manager](https://swift.org/package-manager) to install `SwiftyUtils` by adding the proper description to your `Package.swift` file:\n\n```\nimport PackageDescription\n\nlet package = Package(\n    dependencies: [\n        .Package(url: \"https://github.com/tbaranes/SwiftyUtils.git\", majorVersion: 0)\n    ]\n)\n```\n\n## Feedback\n\n  * If you found a **bug**, open an **issue**\n  * If you have a **feature request**, open an **issue**\n  * If you want to **contribute**, submit a **pull request**\n\n## Contact\n\n* [@tbaranes](https://github.com/tbaranes/) on github\n\n## License\n\nSwiftyUtils is under the MIT license. See the [LICENSE](https://github.com/tbaranes/SwiftyUtils/blob/master/LICENSE) file for more information.\ndic.testAll\n","funding_links":[],"categories":["Libs","Utility [🔝](#readme)","HarmonyOS"],"sub_categories":["Utility","Windows Manager"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftbaranes%2FSwiftyUtils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftbaranes%2FSwiftyUtils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftbaranes%2FSwiftyUtils/lists"}