{"id":3054,"url":"https://github.com/basememara/ZamzamKit","last_synced_at":"2025-08-03T13:31:46.621Z","repository":{"id":33500510,"uuid":"37146425","full_name":"basememara/ZamzamKit","owner":"basememara","description":"A Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and other native frameworks.","archived":false,"fork":false,"pushed_at":"2023-09-23T07:36:59.000Z","size":7706,"stargazers_count":270,"open_issues_count":0,"forks_count":34,"subscribers_count":13,"default_branch":"main","last_synced_at":"2024-11-26T13:25:47.968Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://zamzam.io","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/basememara.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2015-06-09T17:14:20.000Z","updated_at":"2024-09-23T03:51:16.000Z","dependencies_parsed_at":"2024-01-02T21:28:24.931Z","dependency_job_id":null,"html_url":"https://github.com/basememara/ZamzamKit","commit_stats":null,"previous_names":["ZamzamInc/ZamzamKit","zamzaminc/zamzamkit"],"tags_count":148,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/basememara%2FZamzamKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/basememara%2FZamzamKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/basememara%2FZamzamKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/basememara%2FZamzamKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/basememara","download_url":"https://codeload.github.com/basememara/ZamzamKit/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227707766,"owners_count":17807513,"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":[],"created_at":"2024-01-05T20:16:30.084Z","updated_at":"2024-12-07T01:30:28.889Z","avatar_url":"https://github.com/basememara.png","language":"Swift","funding_links":[],"categories":["Utility","Libs","Recently Updated","Utilities and Extensions"],"sub_categories":["Web View","Utility","[Feb 04, 2025](/content/2025/02/04/README.md)"],"readme":"# ZamzamKit\n\n[![Build Status](https://api.travis-ci.org/ZamzamInc/ZamzamKit.svg?branch=master)](https://travis-ci.org/ZamzamInc/ZamzamKit)\n[![Platform](https://img.shields.io/badge/platform-macos%20%7C%20ios%20%7C%20watchos%20%7C%20ipados%20%7C%20tvos-lightgrey)](https://github.com/ZamzamInc/ZamzamKit)\n[![Swift](https://img.shields.io/badge/Swift-5-orange.svg)](https://swift.org)\n[![Xcode](https://img.shields.io/badge/Xcode-14-blue.svg)](https://developer.apple.com/xcode)\n[![SPM](https://img.shields.io/badge/SPM-Compatible-blue)](https://swift.org/package-manager)\n[![MIT](https://img.shields.io/badge/License-MIT-red.svg)](https://opensource.org/licenses/MIT)\n\nZamzamKit is a Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and other native frameworks.\n\n## Installation\n\n### Swift Package Manager\n\n`.package(url: \"git@github.com:ZamzamInc/ZamzamKit.git\", .upToNextMajor(from: \"5.1.0\"))`\n\nThe `ZamzamKit` package contains four different products you can import. Add any combination of these to your target's dependencies within your `Package.swift` manifest:\n```\n.target(\n    name: \"MyAppExample\",\n    dependencies: [\n        .product(name: \"ZamzamCore\", package: \"ZamzamKit\"),\n        .product(name: \"ZamzamLocation\", package: \"ZamzamKit\"),\n        .product(name: \"ZamzamNotification\", package: \"ZamzamKit\"),\n        .product(name: \"ZamzamUI\", package: \"ZamzamKit\"),\n    ]\n)\n```\n*Note: This library is highly volatile and changes often to stay ahead of cutting-edge technologies. It is recommended to copy over code that you want into your own libraries or fork it.*\n\n## ZamzamCore\n\n### Standard+\n\n\u003cdetails\u003e\n\u003csummary\u003eCollection\u003c/summary\u003e\n\n\u003e Get distinct elements from an array:\n```swift\n[1, 1, 3, 3, 5, 5, 7, 9, 9].distinct // [1, 3, 5, 7, 9]\n```\n\n\u003e Remove an element from an array by the value:\n```swift\nvar array = [\"a\", \"b\", \"c\", \"d\", \"e\"]\narray.remove(\"c\")\narray // [\"a\", \"b\", \"d\", \"e\"]\n```\n\n\u003e Easily get the array version of an array slice:\n```swift\n[\"a\", \"b\", \"c\", \"d\", \"e\"].prefix(3).array\n```\n\n\u003e Safely retrieve an element at the given index if it exists:\n```swift\n// Before\nif let items = tabBarController.tabBar.items, items.count \u003e 4 {\n    items[3].selectedImage = UIImage(\"my-image\")\n}\n```\n```swift\n// After\ntabBarController.tabBar.items?[safe: 3]?.selectedImage = UIImage(\"my-image\")\n\n[1, 3, 5, 7, 9][safe: 1] // Optional(3)\n[1, 3, 5, 7, 9][safe: 12] // nil\n```\n\n\u003e Determine if a value is contained within the array of equatable values:\n```swift\n\"b\".within([\"a\", \"b\", \"c\"]) // true\n\nlet status: OrderStatus = .cancelled\nstatus.within([.requested, .accepted, .inProgress]) // false\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eDictionary\u003c/summary\u003e\n\n\u003e Convert to JSON string or data:\n```swift\n// Before\nguard let data = try? JSONSerialization.data(withJSONObject: merged, options: []),\n    let log = String(data: data, encoding: .utf8) else {\n        return\n}\n\n// After\nguard let log = merged.jsonString else {\n    return\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eNumber\u003c/summary\u003e\n\n\u003e Round doubles, floats, or any floating-point type:\n```swift\n123.12312421.rounded(toPlaces: 3) // 123.123\nDouble.pi.rounded(toPlaces: 2) // 3.14\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eString\u003c/summary\u003e\n\n\u003e Create a new random string of given length:\n```swift\nString(random: 10) // \"zXWG4hSgL9\"\nString(random: 4, prefix: \"PIN-\") // \"PIN-uSjm\"\n```\n\n\u003e Safely use subscript indexes and ranges on strings:\n```swift\nlet value = \"Abcdef123456\"\nvalue[3] // \"d\"\nvalue[3..\u003c6] // \"def\"\nvalue[3...6] // \"def1\"\nvalue[3...] // \"def123456\"\nvalue[3...99] // nil\nvalue[99] // nil\n```\n\n\u003e Validate string against common formats:\n```swift\n\"test@example.com\".isEmail // true\n\"123456789\".isNumber // true\n\"zamzam\".isAlpha // true\n\"zamzam123\".isAlphaNumeric // true\n```\n\n\u003e Remove spaces or new lines from both ends:\n```swift\n\" Abcdef123456 \\n\\r  \".trimmed // \"Abcdef123456\"\n```\n\n\u003e Truncate to a given number of characters:\n```swift\n\"Abcdef123456\".truncated(3) // \"Abc...\"\n\"Abcdef123456\".truncated(6, trailing: \"***\") // \"Abcdef***\"\n```\n\n\u003e Determine if a given value is contained:\n```swift\n\"1234567890\".contains(\"567\") // true\n\"abc123xyz\".contains(\"ghi\") // false\n```\n\n\u003e Injects a separator every nth characters:\n```swift\n\"1234567890\".separated(every: 2, with: \"-\") // \"12-34-56-78-90\"\n```\n\n\u003e Remove the characters contained in a given set:\n```swift\nlet string = \"\"\"\n    { 0         1\n    2                  34\n    56       7             8\n    9\n    }\n    \"\"\"\n\nstring.strippingCharacters(in: .whitespacesAndNewlines) // {0123456789}\n```\n\n\u003e Replace the characters contained in a given character set with another string:\n```swift\nlet set = CharacterSet.alphanumerics\n    .insert(charactersIn: \"_\")\n    .inverted\n\nlet string = \"\"\"\n    _abcdefghijklmnopqrstuvwxyz\n    ABCDEFGHIJKLMNOPQRSTUVWXYZ\n    0{1 2\u003c3\u003e4@5#6`7~8?9,0\n\n    1\n    \"\"\"\n\nstring.replacingCharacters(in: set, with: \"_\") //_abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0_1_2_3_4_5_6_7_8_9_0__1\n```\n\n\u003e Match using a regular expression pattern:\n```swift\n\"1234567890\".match(regex: \"^[0-9]+?$\") // true\n\"abc123xyz\".match(regex: \"^[A-Za-z]+$\") // false\n```\n\n\u003e Replace occurrences of a regular expression pattern:\n```swift\n\"aa1bb22cc3d888d4ee5\".replacing(regex: \"\\\\d\", with: \"*\") // \"aa*bb**cc*d***d*ee*\"\n```\n\n\u003e Remove HTML for plain text:\n```swift\n\"\u003cp\u003eThis is \u003cem\u003eweb\u003c/em\u003e content with a \u003ca href=\\\"http://example.com\\\"\u003elink\u003c/a\u003e.\u003c/p\u003e\".htmlStripped // \"This is web content with a link.\"\n```\n\n\u003e Encoders and decoders:\n```swift\nvalue.urlEncoded()\nvalue.urlDecoded()\nvalue.htmlDecoded()\nvalue.base64Encoded()\nvalue.base64Decoded()\nvalue.base64URLEncoded()\n```\n\n\u003e Get an encrypted version of the string in hex format:\n```swift\n\"test@example.com\".sha256() // 973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b\n```\n\n\u003e Easily get the string version of substring:\n```swift\n\"hello world\".prefix(5).string\n```\n\n\u003e Determine if an optional string is `nil` or has no characters\n```swift\nvar value: String? = \"test 123\"\nvalue.isNilOrEmpty\n```\n\u003c/details\u003e\n\n### Foundation+\n\n\u003cdetails\u003e\n\u003csummary\u003eBundle\u003c/summary\u003e\n\n\u003e Get the string from a file within any bundle:\n```swift\nBundle.main.string(file: \"Test.txt\") // \"This is a test. Abc 123.\\n\"\n```\n\n\u003e Get a generic array from a property list file within any bundle:\n\n```swift\nlet values: [String] = Bundle.main.array(plist: \"Array.plist\")\n\nvalues[0] // \"Abc\"\nvalues[1] // \"Def\"\nvalues[2] // \"Ghi\"\n```\n\n![Image of BundleArray](./Documentation/Images/BundleArray.png)\n\n```swift\nlet values: [[String: Any]] = Bundle.main.array(plist: \"Things.plist\")\n\nvalues[0][\"id\"] as? Int // 1\nvalues[0][\"name\"] as? String // \"Test 1\"\nvalues[0][\"description\"] as? String // \"This is a test for 1.\")\n\nvalues[1][\"id\"] as? Int // 2)\nvalues[1][\"name\"] as? String // \"Test 2\")\nvalues[1][\"description\"] as? String // \"This is a test for 2.\")\n\nvalues[2][\"id\"] as? Int // 3)\nvalues[2][\"name\"] as? String // \"Test 3\")\nvalues[2][\"description\"] as? String // \"This is a test for 3.\")\n```\n\n![Image of BundleArray](./Documentation/Images/BundleArray2.png)\n\n\u003e Get a dictionary from a property list file within any bundle:\n```swift\nlet values: [String: Any] = Bundle.main.contents(plist: \"Settings.plist\")\n\nvalues[\"MyString1\"] as? String // \"My string value 1.\"\nvalues[\"MyNumber1\"] as? Int // 123\nvalues[\"MyBool1\"] as? Bool // false\nvalues[\"MyDate1\"] as? Date // 2018-11-21 15:40:03 +0000\n```\n\n![Image of BundleDictionary](./Documentation/Images/BundleDictionary.png)\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eColor\u003c/summary\u003e\n\n\u003e Additional color initializers:\n```swift\nUIColor(hex: 0x990000)\nUIColor(hex: 0x4286F4)\nUIColor(rgb: (66, 134, 244))\nUIColor.random\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eCurrency\u003c/summary\u003e\n\n\u003e A formatter that converts between numeric values and their textual currency representations:\n```swift\nlet formatter = CurrencyFormatter()\nformatter.string(fromAmount: 123456789.987) // \"$123,456,789.99\"\n\nlet formatter2 = CurrencyFormatter(for: Locale(identifier: \"fr-FR\"))\nformatter2.string(fromCents: 123456789) // \"1 234 567,89 €\"\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eData\u003c/summary\u003e\n\n\u003e Get a hex string representation of the data:\n```swift\nData()?.hexString() // 68626a4a424a6a68626a68616420663773376474663720737567796f3837545e49542a69797567\n```\n\n\u003e Get an encrypted version of the data:\n```swift\nData()?.sha256()\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eDate\u003c/summary\u003e\n\n\u003e Determine if a date is in the past or future:\n```swift\nDate(timeIntervalSinceNow: -100).isPast // true\nDate(timeIntervalSinceNow: 100).isPast // false\n\nDate(timeIntervalSinceNow: 100).isFuture // true\nDate(timeIntervalSinceNow: -100).isFuture // false\n```\n\n\u003e Determine if a date is today, yesterday, or tomorrow:\n```swift\nDate().isToday // true\nDate(timeIntervalSinceNow: -90_000).isYesterday // true\nDate(timeIntervalSinceNow: 90_000).isTomorrow // true\n```\n\n\u003e Determine if a date is within a weekday or weekend period:\n```swift\nDate().isWeekday // false\nDate().isWeekend // true\n```\n\n\u003e Get the beginning or end of the day:\n```swift\nDate().startOfDay // \"2018/11/21 00:00:00\"\nDate().endOfDay // \"2018/11/21 23:59:59\"\n```\n\n\u003e Get the beginning or end of the month:\n```swift\nDate().startOfMonth // \"2018/11/01 00:00:00\"\nDate().endOfMonth // \"2018/11/30 23:59:59\"\n```\n\n\u003e Determine if a date is current:\n```swift\nlet date = Date(fromString: \"2018/03/22 09:40\")\ndate.isCurrentWeek\ndate.isCurrentMonth\ndate.isCurrentYear\n```\n\n\u003e Determine if a date is between two other dates:\n```swift\nlet date = Date()\nlet date1 = Date(timeIntervalSinceNow: 1000)\nlet date2 = Date(timeIntervalSinceNow: -1000)\n\ndate.isBetween(date1, date2) // true\n```\n\n\u003e Determine if a date is beyond a specified time window:\n```swift\nlet date = Date(fromString: \"2018/03/22 09:40\")\nlet fromDate = Date(fromString: \"2018/03/22 09:30\")\n\ndate.isBeyond(fromDate, bySeconds: 300) // true\ndate.isBeyond(fromDate, bySeconds: 1200) // false\n```\n\n\u003e Use specific calendar for data manipulations:\n```swift\nlet date = Date(fromString: \"2018/03/22 09:40\")\nlet calendar = Calendar(identifier: .chinese)\n\ndate.isToday(using: calendar)\ndate.isWeekday(using: calendar)\ndate.isCurrentMonth(using: calendar)\ndate.isToday(using: calendar)\ndate.startOfDay(using: calendar)\ndate.startOfMonth(using: calendar)\n```\n\n\u003e Determine if a date is beyond a specified time window:\n```swift\nlet date = Date(fromString: \"2018/03/22 09:40\")\nlet fromDate = Date(fromString: \"2018/03/22 09:30\")\n\ndate.isBeyond(fromDate, bySeconds: 300) // true\ndate.isBeyond(fromDate, bySeconds: 1200) // false\n```\n\n\u003e Create a date from a string:\n```swift\nDate(fromString: \"2018/11/01 18:15\")\nDate(fromString: \"1440/03/01 18:31\", calendar: Calendar(identifier: .islamic))\n```\n\n\u003e Format a date to a string:\n```swift\nDate().string(format: \"MMM d, h:mm a\") // \"Jan 3, 8:43 PM\"\nDate().string(style: .full, calendar: Calendar(identifier: .hebrew)) // \"Friday, 1 Kislev 5779\"\nDate().string(formatter: .MM_dd_yyyy_HH_mm)\n```\n\n\u003e Format a time interval to display as a timer.\n```swift\nlet date = Date(fromString: \"2016/03/22 09:45\")\nlet fromDate = Date(fromString: \"2016/03/22 09:40\")\n\ndate.timerString(from: fromDate)\n\n// Prints \"00:05:00\"\n```\n\n\u003e Get the decimal representation of the time:\n```swift\nDate(fromString: \"2018/10/23 18:15\").timeToDecimal // 18.25\n```\n\n\u003e Increment years, months, days, hours, or minutes:\n```swift\nlet date = Date()\ndate + .years(1)\ndate + .months(2)\ndate - .days(4)\ndate - .hours(6)\ndate + .minutes(12)\ndate + .days(5, Calendar(identifier: .chinese))\n```\n\n\u003e Convert between time interval units:\n```swift\nlet diff = date.timeIntervalSince(date2) // 172,800 seconds\ndiff.minutes // 2,800 minutes\ndiff.hours // 48 hours\ndiff.days // 2 days\n```\n\n\u003e Time zone context and offset:\n```swift\nlet timeZone = TimeZone(identifier: \"Europe/Paris\")\ntimeZone?.isCurrent // false\ntimeZone?.offsetFromCurrent // -21600\n```\n\n\u003e Normalize date calculations and data storage:\n```swift\nlet timeZone: TimeZone = .posix // GMT\nlet locale: Locale = .posix // en_US_POSIX\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eDecodable\u003c/summary\u003e\n\n\u003e Get a value of the type you specify, decoded from a JSON string.\n```swift\nlet jsonString = \"{\\\"test1\\\":29,\\\"test2\\\":62,\\\"test3\\\":33,\\\"test4\\\":24,\\\"test5\\\":14,\\\"test6\\\":72}\"\nlet jsonObject: [String: Int] = jsonString.decode()\n\n// Result\n[\n    \"test1\": 29,\n    \"test2\": 62,\n    \"test3\": 33,\n    \"test4\": 24,\n    \"test5\": 14,\n    \"test6\": 72\n]\n```\n\n\u003e Get a type-erased `Decodable` value:\n```swift\nlet json = \"\"\"\n{\n    \"boolean\": true,\n    \"integer\": 1,\n    \"double\": 3.14159265358979323846,\n    \"string\": \"Abc123\",\n    \"date\": \"2018-12-05T15:28:25+00:00\",\n    \"array\": [1, 2, 3],\n    \"nested\": {\n        \"a\": \"alpha\",\n        \"b\": \"bravo\",\n        \"c\": \"charlie\"\n    }\n}\n\"\"\".data(using: .utf8)\n\nlet decoder = JSONDecoder()\nlet dictionary = try decoder.decode([String: AnyDecodable].self, from: json)\n\ndictionary[\"boolean\"].value // true\ndictionary[\"integer\"].value // 1\ndictionary[\"string\"].value // Abc123\n```\n\n\u003e Skip failed elements during decoding instead exiting collection completely; lossy array decoding.\n```swift\ninit(from decoder: Decoder) throws {\n    let container = try decoder.container(keyedBy: CodingKeys.self)\n    \n    self.authors = try container.decode(FailableCodableArray\u003cAuthor\u003e.self, forKey: .author)\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eDispatchQueue\u003c/summary\u003e\n\n\u003e Provides configured queues for executing commonly related work items:\n```swift\nDispatchQueue.database.async {\n    // Database work here\n}\n\nDispatchQueue.transform.async {\n    // Parse or decode work here\n}\n\nDispatchQueue.logger.async {\n    // Logging work here\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eFileManager\u003c/summary\u003e\n\n\u003e Get URL or file system path for a file:\n```swift\nFileManager.default.url(of: fileName, from: .documentDirectory)\nFileManager.default.path(of: fileName, from: .cachesDirectory)\n```\n\n\u003e Get URL or file system paths of files within a directory:\n```swift\nFileManager.default.urls(from: .documentDirectory)\nFileManager.default.paths(from: .downloadsDirectory)\n```\n\n\u003e Retrieve a file remotely and persist to local disk:\n```swift\nFileManager.default.download(from: \"http://example.com/test.pdf\") { url, response, error in\n    // The `url` parameter represents location on local disk where remote file was downloaded.\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eLocation\u003c/summary\u003e\n\n\u003e Get the location details for coordinates:\n```swift\nCLLocation(latitude: 43.6532, longitude: -79.3832).geocoder { meta in\n    print(meta.locality)\n    print(meta.country)\n    print(meta.countryCode)\n    print(meta.timezone)\n    print(meta.administrativeArea)\n}\n```\n\n\u003e Get the closest or farthest location from a list of coordinates:\n```swift\nlet coordinates = [\n    CLLocationCoordinate2D(latitude: 43.6532, longitude: -79.3832),\n    CLLocationCoordinate2D(latitude: 59.9094, longitude: 10.7349),\n    CLLocationCoordinate2D(latitude: 35.7750, longitude: -78.6336),\n    CLLocationCoordinate2D(latitude: 33.720817, longitude: 73.090032)\n]\n\ncoordinates.closest(to: homeCoordinate)\ncoordinates.farthest(from: homeCoordinate)\n```\n\n\u003e Determine if location services is enabled and authorized for always or when in use:\n```swift\nCLLocationManager.isAuthorized // bool\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eURL\u003c/summary\u003e\n\n\u003e Append a query string parameter to a URL:\n```swift\nlet url = URL(string: \"https://example.com?abc=123\u0026lmn=tuv\u0026xyz=987\")\nurl?.appendingQueryItem(\"def\", value: \"456\") // \"https://example.com?abc=123\u0026lmn=tuv\u0026xyz=987\u0026def=456\"\nurl?.appendingQueryItem(\"xyz\", value: \"999\") // \"https://example.com?abc=123\u0026lmn=tuv\u0026xyz=999\"\n```\n\n\u003e Append a dictionary of query string parameters to a URL:\n```swift\nlet url = URL(string: \"https://example.com?abc=123\u0026lmn=tuv\u0026xyz=987\")\nurl?.appendingQueryItems([\n    \"def\": \"456\",\n    \"jkl\": \"777\",\n    \"abc\": \"333\",\n    \"lmn\": nil\n]) // \"https://example.com?xyz=987\u0026def=456\u0026abc=333\u0026jkl=777\"\n```\n\n\u003e Remove a query string parameter to a URL:\n```swift\nlet url = URL(string: \"https://example.com?abc=123\u0026lmn=tuv\u0026xyz=987\")\nurl?.removeQueryItem(\"xyz\") // \"https://example.com?abc=123\u0026lmn=tuv\"\n```\n\n\u003e Query a URL from a parameter name:\n```swift\nlet url = URL(string: \"https://example.com?abc=123\u0026lmn=tuv\u0026xyz=987\")\nurl?.queryItem(\"aBc\") // \"123\"\nurl?.queryItem(\"lmn\") // \"tuv\"\nurl?.queryItem(\"yyy\") // nil\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eURLSession\u003c/summary\u003e\n\n\u003e A thin wrapper around `URLSession` and `URLRequest` for simple network requests:\n```swift\nlet request = URLRequest(\n    url: URL(string: \"https://httpbin.org/get\")!,\n    method: .get,\n    parameters: [\n        \"abc\": 123,\n        \"def\": \"test456\",\n        \"xyz\": true\n    ],\n    headers: [\n        \"Abc\": \"test123\",\n        \"Def\": \"test456\",\n        \"Xyz\": \"test789\"\n    ]\n)\n \nlet networkManager = NetworkManager(\n    service: NetworkFoundationService()\n)\n\nnetworkManager.send(with: request) { result in\n    switch result {\n    case let .success(response):\n        response.data\n        response.headers\n        response.statusCode\n    case let .failure(error):\n        error.statusCode\n    }\n}\n```\nOr call multiple URL requests simultaneously:\n```swift\nlet request1 = URLRequest(\n    url: URL(string: \"https://httpbin.org/get\")!,\n    method: .get\n)\n\nlet request2 = URLRequest(\n    url: URL(string: \"https://httpbin.org/post\")!,\n    method: .post\n)\n\nlet request3 = URLRequest(\n    url: URL(string: \"https://httpbin.org/delete\")!,\n    method: .delete\n)\n\nnetworkManager.send(requests: request1, request2, request3) { firstResult, anotherResult, otherResult in\n    switch firstResult {\n    case let .success(response):\n        response.data\n    case let .failure(error):\n        error.statusCode\n    }\n\n    switch anotherResult {\n    case let .success(response):\n        response.data\n    case let .failure(error):\n        error.statusCode\n    }\n    \n    switch otherResult {\n    case let .success(response):\n        response.data\n    case let .failure(error):\n        error.statusCode\n    }\n}\n```\n\n\u003e Use an adapter to intercept any `URLRequest` and modify for all network requests:\n```swift\nstruct CustomURLRequestAdapter: URLRequestAdapter {\n        \n    func adapt(_ request: URLRequest) -\u003e URLRequest {\n        var request = request\n        request.setValue(\"1\", forHTTPHeaderField: \"X-Test-1\")\n        request.setValue(\"2\", forHTTPHeaderField: \"X-Test-2\")\n        return request\n    }\n}\n\nlet request = URLRequest(\n    url: URL(string: \"https://httpbin.org/get\")!,\n    method: .get\n)\n \nlet networkManager = NetworkManager(\n    service: NetworkFoundationService(),\n    adapter: CustomURLRequestAdapter()\n)\n\nnetworkManager.send(with: request) { result in\n    guard case let .success(response) else { return }\n\n    request.value(forHTTPHeaderField: \"X-Test-1\") == nil // true\n    request.value(forHTTPHeaderField: \"X-Test-2\") == nil // true\n\n    response.request.value(forHTTPHeaderField: \"X-Test-1\") == \"1\" // true\n    response.request.value(forHTTPHeaderField: \"X-Test-2\") == \"2\" // true\n}\n```\n\u003c/details\u003e\n\n### Utilities\n\n\u003cdetails\u003e\n\u003csummary\u003eAppInfo\u003c/summary\u003e\n\n\u003e Get details of the current app:\n```swift\nstruct SomeStruct: AppInfo {\n\n}\n\nlet someStruct = SomeStruct()\n\nsomeStruct.appDisplayName // \"Zamzam App\"\nsomeStruct.appBundleID // \"io.zamzam.app\"\nsomeStruct.appVersion // \"1.0.0\"\nsomeStruct.appBuild // \"23\"\nsomeStruct.isInTestFlight // false\nsomeStruct.isRunningOnSimulator // false\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eApply\u003c/summary\u003e\n\n\u003e Set properties with closures just after initializing:\n```swift\nlet paragraph = NSMutableParagraphStyle().apply {\n    $0.alignment = .center\n    $0.lineSpacing = 8\n}\n\nlet label = UILabel().apply {\n    $0.textAlignment = .center\n    $0.textColor = UIColor.black\n    $0.text = \"Hello, World!\"\n}\n\nUITabBar.appearance().apply {\n    $0.barStyle = .dark\n    $0.tintColor = .blue\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eAtomic\u003c/summary\u003e\n\n\u003e A thread-safe value that handles concurrent reads and writes ([read more](https://basememara.com/creating-thread-safe-generic-values-in-swift/)):\n```swift\nvar temp = Atomic\u003cInt\u003e(0)\n\nDispatchQueue.concurrentPerform(iterations: 1_000_000) { index in\ntemp.value { $0 += 1 }\n}\n\nXCTAssertEqual(temp.value, 1_000_000) // true\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eAppMigration\u003c/summary\u003e\n\n\u003e Manages blocks of code that only need to run once on version updates in apps:\n```swift\n@UIApplicationMain\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n\n    var window: UIWindow?\n    let migration = AppMigration()\n\n    func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -\u003e Bool {\n        migration\n            .performUpdate {\n                print(\"Migrate update occurred.\")\n            }\n            .perform(forVersion: \"1.0\") {\n                print(\"Migrate to 1.0 occurred.\")\n            }\n            .perform(forVersion: \"1.7\") {\n                print(\"Migrate to 1.7 occurred.\")\n            }\n            .perform(forVersion: \"2.4\") {\n                print(\"Migrate to 2.4 occurred.\")\n            }\n            \n        return true\n    }\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eBackgroundTask\u003c/summary\u003e\n\n\u003e Easily execute a [long-running background task](https://developer.apple.com/documentation/uikit/uiapplication/1623031-beginbackgroundtask):\n```swift\nBackgroundTask.run(for: application) { task in\n    // Perform finite-length task...\n    task.end()\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eKeychain\u003c/summary\u003e\n\n\u003e A thin wrapper to manage Keychain, or other services that conform to `KeychainService`:\n```swift\nlet keychain = KeychainManager(\n    service: KeychainExternalService()\n)\n\nkeychain.set(\"kjn989hi\", forKey: .token)\n\nkeychain.get(.token) {\n    print($0) // \"kjn989hi\"\n}\n\n// Define strongly-typed keys\nextension KeychainAPI.Key {\n    static let token = KeychainAPI.Key(\"token\")\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eLogger\u003c/summary\u003e\n\n\u003e Create loggers that conform to `LogService` and add to `LogManager` (console and `os_log` are included):\n```swift\nlet log = LogManager(\n    services: [\n        LogServiceConsole(minLevel: .debug),\n        LogServiceOS(\n            minLevel: .warning,\n            subsystem: \"io.zamzam.Basem-Emara\",\n            category: \"Application\"\n        ),\n        MyCustomLogger()\n    ]\n)\n\nlog.error(\"There was an error.\")\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eSystemConfiguration\u003c/summary\u003e\n\n\u003e Determine if the device is connected to a network:\n```swift\nimport SystemConfiguration\n\nSCNetworkReachability.isOnline\n```\n\u003c/details\u003e\n\n### Infixes\n\n\u003cdetails\u003e\n\u003csummary\u003eConditionalAssignment ?=\u003c/summary\u003e\n\n\u003e Assign a value if not nil:\n```swift\nvar test: Int? = 123\nvar value: Int? = nil\n\ntest ?= value\n// test == 123\n\nvalue = 456\ntest ?= value\n// test == 456\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eNilOrEmptyAssignment ??+\u003c/summary\u003e\n\n\u003e Assign a value if not nil or empty:\n```swift\nvar test: String\nvar value: String?\n\ntest = value ??+ \"Abc\"\n// test == \"Abc\"\n\nvalue = \"\"\ntest = value ??+ \"Lmn\"\n// test == \"Lmn\"\n\nvalue = \"Xyz\"\ntest = value ??+ \"Rst\"\n// test == \"Xyz\"\n```\n\u003c/details\u003e\n\n## ZamzamLocation\n\n\u003cdetails\u003e\n\u003csummary\u003eLocationManager\u003c/summary\u003e\n\n\u003e Location manager that offers `Combine` wrappers:\n```swift\n\nfunc fetchLocation() {\n    log.debug(\"Begin location authorization...\")\n\n    guard locationManager.isAuthorized else {\n        locationManager.requestAuthorization()\n            .handleEvents(receiveOutput: { [weak self] granted in\n                guard granted else {\n                    self?.log.error(\"Location authorization denied\")\n                    return\n                }\n\n                self?.log.debug(\"Location authorization granted\")\n            })\n            .first { $0 }\n            .sink { [weak self] _ in self?.fetchLocation() }\n            .store(in: \u0026cancellable)\n\n        return\n    }\n\n    log.debug(\"Begin fetching location...\")\n\n    locationManager\n        .startUpdatingLocation()\n        .retry(3)\n        .catch { [weak self] error -\u003e AnyPublisher\u003cCLLocation, Never\u003e in\n            self?.log.error(\"GPS location coordinate failed\", error: error)\n            return Empty(completeImmediately: true).eraseToAnyPublisher()\n        }\n        .first()\n        .sink { [weak self] in\n            self?.log.debug(\"Location coordinate: \\($0)\")\n            self?.locationManager.stopUpdatingLocation()\n            self?.log.debug(\"Location turned off GPS\")\n        }\n        .store(in: \u0026cancellable)\n}\n```\n\u003c/details\u003e\n\n## ZamzamNotification\n\n\u003cdetails\u003e\n\u003csummary\u003eUserNotification\u003c/summary\u003e\n\n\u003e Registers the local and remote notifications with the categories and actions it supports:\n```swift\nUNUserNotificationCenter.current().register(\n    delegate: self,\n    categories: [\n        \"order\": [\n            UNNotificationAction(\n                identifier: \"confirmAction\",\n                title: \"Confirm\",\n                options: [.foreground]\n            )\n        ],\n        \"chat\": [\n            UNTextInputNotificationAction(\n                identifier: \"replyAction\",\n                title: \"Reply\",\n                options: [],\n                textInputButtonTitle: \"Send\",\n                textInputPlaceholder: \"Type your message\"\n            )\n        ],\n        \"offer\": nil\n    ],\n    authorizations: [.alert, .badge, .sound],\n    completion: { granted in\n        granted\n            ? log.debug(\"Authorization for notification succeeded.\")\n            : log.warn(\"Authorization for notification not given.\")\n    }\n)\n```\n\n\u003e Get a list of all pending or delivered user notifications:\n```swift\nUNUserNotificationCenter.current().getNotificationRequests { notifications in\n    notifications.forEach {\n        print($0.identifier)\n    }\n}\n```\n\n\u003e Find the pending or delivered notification request by identifier:\n```swift\nUNUserNotificationCenter.current().get(withIdentifier: \"abc123\") {\n    print($0?.identifier)\n}\n\nUNUserNotificationCenter.current().get(withIdentifiers: [\"abc123\", \"xyz789\"]) {\n    $0.forEach {\n        print($0.identifier)\n    }\n}\n```\n\n\u003e Determine if the pending or delivered notification request exists:\n```swift\nUNUserNotificationCenter.current().exists(withIdentifier: \"abc123\") {\n    print(\"Does notification exist: \\($0)\")\n}\n```\n\n\u003e Schedules local notifications for delivery:\n```swift\nUNUserNotificationCenter.current().add(\n    body: \"This is the body for time interval\",\n    timeInterval: 5\n)\n\nUNUserNotificationCenter.current().add(\n    body: \"This is the body for time interval\",\n    title: \"This is the snooze title\",\n    timeInterval: 60,\n    identifier: \"abc123-main\"\n)\n\nUNUserNotificationCenter.current().add(\n    body: \"This is the body for time interval\",\n    title: \"This is the misc1 title\",\n    timeInterval: 60,\n    identifier: \"abc123-misc1\",\n    category: \"misc1Category\"\n)\n\nUNUserNotificationCenter.current().add(\n    body: \"This is the body for time interval\",\n    title: \"This is the misc2 title\",\n    timeInterval: 60,\n    identifier: \"abc123-misc2\",\n    category: \"misc2Category\",\n    userInfo: [\n        \"id\": post.id,\n        \"link\": post.link,\n        \"mediaURL\": mediaURL\n    ],\n    completion: { error in\n        guard error == nil else { return }\n        // Added successfully\n    }\n)\n\nUNUserNotificationCenter.current().add(\n    date: Date(timeIntervalSinceNow: 5),\n    body: \"This is the body for date\",\n    repeats: .minute,\n    identifier: \"abc123-repeat\"\n)\n```\n\n\u003e Get a remote image from the web and convert to a user notification attachment:\n```swift\nUNNotificationAttachment.download(from: urlString) {\n    guard case let .success(attachment) = $0 else {\n        log.error(\"Could not download the remote resource (\\(urlString)): \\($0.error?.debugDescription).\")\n        return\n    }\n\n    UNUserNotificationCenter.current().add(\n        body: \"This is the body\",\n        attachments: [attachment]\n    )\n}\n```\n\n\u003e Remove pending or delivered notification requests by identifiers, categories, or all:\n```swift\nUNUserNotificationCenter.current().remove(withIdentifier: \"abc123\")\nUNUserNotificationCenter.current().remove(withIdentifiers: [\"abc123\", \"xyz789\"])\nUNUserNotificationCenter.current().remove(withCategory: \"chat\") { /* Done */ }\nUNUserNotificationCenter.current().remove(withCategories: [\"order\", \"chat\"]) { /* Done */ }\nUNUserNotificationCenter.current().removeAll()\n```\n\u003c/details\u003e\n\n## ZamzamUI\n\n### SwiftUI\n\n\u003cdetails\u003e\n\u003csummary\u003eDocumentation coming soon!\u003c/summary\u003e\n\n\u003eExtensions\n\u003eModifiers\n\u003eStyles\n\u003eViews\n\u003c/details\u003e\n\n\n### WatchKit\n\n\u003cdetails\u003e\n\u003csummary\u003eCLKComplicationServer\u003c/summary\u003e\n\n\u003e Invalidates and reloads all timeline data for all complications:\n```swift\n// Before\nguard let complications = activeComplications, !complications.isEmpty else { return }\ncomplications.forEach { reloadTimeline(for: $0) }\n```\n```swift\n// After\nCLKComplicationServer.sharedInstance().reloadTimelineForComplications()\n```\n\n\u003e Extends all timeline data for all complications:\n```swift\n// Before\nguard let complications = activeComplications, !complications.isEmpty else { return }\ncomplications.forEach { extendTimeline(for: $0) }\n```\n```swift\n// After\nCLKComplicationServer.sharedInstance().extendTimelineForComplications()\n```\n\u003c/details\u003e\n\n## Author\n\n* Basem Emara, https://zamzam.io\n\n## License\n\n`ZamzamKit` is available under the MIT license. See the [LICENSE](https://github.com/ZamzamInc/ZamzamKit/blob/master/LICENSE) file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbasememara%2FZamzamKit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbasememara%2FZamzamKit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbasememara%2FZamzamKit/lists"}