Skip to content

Commit

Permalink
Merge pull request #17 from ZamzamInc/develop
Browse files Browse the repository at this point in the history
Merge develop into master
  • Loading branch information
basememara authored Mar 7, 2020
2 parents 77d4407 + 3108cbb commit fd0a70b
Show file tree
Hide file tree
Showing 75 changed files with 3,794 additions and 1,527 deletions.
197 changes: 113 additions & 84 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ tabBarController.tabBar.items?[safe: 3]?.selectedImage = UIImage("my-image")
"b".within(["a", "b", "c"]) // true

let status: OrderStatus = .cancelled
status.within([.requeseted, .accepted, .inProgress]) // false
status.within([.requested, .accepted, .inProgress]) // false
```
</details>

Expand Down Expand Up @@ -273,6 +273,41 @@ value[99] // nil
"1234567890".separated(every: 2, with: "-") // "12-34-56-78-90"
```

> Remove the characters contained in a given set:
```swift
let string = """
{ 0 1
2 34
56 7 8
9
}
"""

string.strippingCharacters(in: .whitespacesAndNewlines) // {0123456789}
```

> Replace the characters contained in a givenharacter set with another string:
```swift
let set = CharacterSet.alphanumerics
.insert(charactersIn: "_")
.inverted

let string = """
_abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
0{1 2<3>4@5#6`7~8?9,0
1
"""

string.replacingCharacters(in: set, with: "_") //_abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0_1_2_3_4_5_6_7_8_9_0__1
```

> Get an encrypted version of the string in hex format:
```swift
"[email protected]".sha256() // 973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b
```

> Match using a regular expression pattern:
```swift
"1234567890".match(regex: "^[0-9]+?$") // true
Expand Down Expand Up @@ -309,36 +344,6 @@ value.base64URLEncoded
var value: String? = "test 123"
value.isNilOrEmpty
```

> Strongly-typed string keys:
```swift
// First define keys
extension String.Keys {
static let testString = String.Key<String?>("testString")
static let testInt = String.Key<Int?>("testInt")
static let testBool = String.Key<Bool?>("testBool")
static let testArray = String.Key<[Int]?>("testArray")
}

// Create method or subscript for any type
extension UserDefaults {

subscript<T>(key: String.Key<T?>) -> T? {
get { object(forKey: key.name) as? T }

set {
guard let value = newValue else { return remove(key) }
set(value, forKey: key.name)
}
}
}

// Then use strongly-typed values
let testString: String? = UserDefaults.standard[.testString]
let testInt: Int? = UserDefaults.standard[.testInt]
let testBool: Bool? = UserDefaults.standard[.testBool]
let testArray: [Int]? = UserDefaults.standard[.testArray]
```
</details>

### Foundation+
Expand Down Expand Up @@ -573,23 +578,60 @@ label.attributedText = "Abc".attributed + " def " +
</details>

<details>
<summary>URL</summary>
<summary>URLSession</summary>

> A thin wrapper around `URLSession` and `URLRequest` for simple network requests:
```swift
let request = URLRequest(
url: URL(string: "https://httpbin.org/get")!,
method: .get,
parameters: [
"abc": 123,
"def": "test456",
"xyz": true
],
headers: [
"Abc": "test123",
"Def": "test456",
"Xyz": "test789"
]
)

let networkProvider: NetworkProviderType = NetworkProvider(
store: NetworkURLSessionStore()
)

networkProvider.send(with: request) { result in
switch result {
case .success(let response):
response.data
response.headers
response.statusCode
case .failure(let error):
error.statusCode
}
}
```
</details>

> Append or remove query string parameters:
```swift
let url = URL(string: "https://example.com?abc=123&lmn=tuv&xyz=987")
<details>
<summary>UserDefaults</summary>

url?.appendingQueryItem("def", value: "456") // "https://example.com?abc=123&lmn=tuv&xyz=987&def=456"
url?.appendingQueryItem("xyz", value: "999") // "https://example.com?abc=123&lmn=tuv&xyz=999"
> A thin wrapper to manage `UserDefaults`, or other storages that conform to `PreferencesStore`:
```swift
let preferences: PreferencesType = Preferences(
store: PreferencesDefaultsStore(
defaults: UserDefaults.standard
)
)

url?.appendingQueryItems([
"def": "456",
"jkl": "777",
"abc": "333",
"lmn": nil
]) -> "https://example.com?xyz=987&def=456&abc=333&jkl=777"
preferences.set(123, forKey: .abc)
preferences.get(.token) // 123

url?.removeQueryItem("xyz") // "https://example.com?abc=123&lmn=tuv"
// Define strongly-typed keys
extension PreferencesAPI.Keys {
static let abc = PreferencesAPI.Key<String>("abc")
}
```
</details>

Expand Down Expand Up @@ -726,43 +768,30 @@ BackgroundTask.run(for: application) { task in
```
</details>

### Utilities

<details>
<summary>Dependencies</summary>
<summary>Keychain</summary>

> Lightweight dependency injection via property wrapper ([read more](https://basememara.com/swift-dependency-injection-via-property-wrapper/)):
> A thin wrapper to manage Keychain, or other storages that conform to `SecuredPreferencesStore`:
```swift
class AppDelegate: UIResponder, UIApplicationDelegate {

private let dependencies = Dependencies {
Module { WidgetModule() as WidgetModuleType }
Module { SampleModule() as SampleModuleType }
}

override init() {
super.init()
dependencies.build()
}
}
let keychain: SecuredPreferencesType = SecuredPreferences(
store: SecuredPreferencesKeychainStore()
)

// Some time later...
keychain.set("kjn989hi", forKey: .token)

class ViewController: UIViewController {

@Inject private var widgetService: WidgetServiceType
@Inject private var sampleService: SampleServiceType

override func viewDidLoad() {
super.viewDidLoad()

print(widgetService.test())
print(sampleService.test())
}
keychain.get(.token) {
print($0) // "kjn989hi"
}

// Define strongly-typed keys
extension SecuredPreferencesAPI.Key {
static let token = SecuredPreferencesAPI.Key("token")
}
```
</details>

### Utilities

<details>
<summary>Localization</summary>

Expand All @@ -785,9 +814,9 @@ myLabel3.text = .localized(.next)
<details>
<summary>Logger</summary>

> Create loggers that conform to `LogStore` and add to `LogWorker` (console and `os_log` are included):
> Create loggers that conform to `LogStore` and add to `LogProvider` (console and `os_log` are included):
```swift
let log: LogWorkerType = LogWorker(
let log: LogProviderType = LogProvider(
stores: [
LogConsoleStore(minLevel: .debug),
LogOSStore(
Expand Down Expand Up @@ -954,54 +983,54 @@ test = value ??+ "Rst"
## ZamzamLocation

<details>
<summary>LocationsWorker</summary>
<summary>LocationsProvider</summary>

> Location worker that offers easy authorization and observable closures ([read more](https://basememara.com/swifty-locations-observables/)):
```swift
class LocationViewController: UIViewController {

@IBOutlet weak var outputLabel: UILabel!

var locationsWorker: LocationsWorkerType = LocationsWorker(
var locationsProvider: LocationsProviderType = LocationsProvider(
desiredAccuracy: kCLLocationAccuracyThreeKilometers,
distanceFilter: 1000
)

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

locationsWorker.addObserver(locationObserver)
locationsWorker.addObserver(headingObserver)
locationsProvider.addObserver(locationObserver)
locationsProvider.addObserver(headingObserver)

locationsWorker.requestAuthorization(
locationsProvider.requestAuthorization(
for: .whenInUse,
startUpdatingLocation: true,
completion: { granted in
guard granted else { return }
self.locationsWorker.startUpdatingHeading()
self.locationsProvider.startUpdatingHeading()
}
)
}

override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
locationsWorker.removeObservers()
locationsProvider.removeObservers()
}

deinit {
locationsWorker.removeObservers()
locationsProvider.removeObservers()
}
}

extension LocationViewController {

var locationObserver: Observer<LocationsWorker.LocationHandler> {
var locationObserver: Observer<LocationsProvider.LocationHandler> {
return Observer { [weak self] in
self?.outputLabel.text = $0.description
}
}

var headingObserver: Observer<LocationsWorker.HeadingHandler> {
var headingObserver: Observer<LocationsProvider.HeadingHandler> {
return Observer {
print($0.description)
}
Expand Down
5 changes: 2 additions & 3 deletions Sources/ZamzamCore/Application/AppInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,9 @@ public extension AppInfo {
var isRunningOnSimulator: Bool {
// http://stackoverflow.com/questions/24869481/detect-if-app-is-being-built-for-device-or-simulator-in-swift
#if targetEnvironment(simulator)
return true
return true
#else
return false
return false
#endif
}

}
25 changes: 18 additions & 7 deletions Sources/ZamzamCore/Application/ApplicationPluggableDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
//
// ApplicationPluggableDelegate.swift
// ZamzamKit iOS
// https://github.com/fmo91/PluggableApplicationDelegate
//
// Created by Basem Emara on 2018-01-28.
// https://github.com/fmo91/PluggableApplicationDelegate
//
// Copyright © 2018 Zamzam Inc. All rights reserved.
//

Expand All @@ -12,7 +13,7 @@ import UIKit

/// Subclassed by the `AppDelegate` to pass lifecycle events to loaded plugins.
///
/// The application plugins will be processed in sequence after calling `application() -> [ApplicationPlugin]`.
/// The application plugins will be processed in sequence after calling `plugins() -> [ApplicationPlugin]`.
///
/// @UIApplicationMain
/// class AppDelegate: ApplicationPluggableDelegate {
Expand All @@ -25,7 +26,7 @@ import UIKit
///
/// Each application plugin has access to the `AppDelegate` lifecycle events:
///
/// final class LoggerPlugin: ApplicationPlugin {
/// struct LoggerPlugin: ApplicationPlugin {
/// private let log = Logger()
///
/// func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Expand All @@ -39,14 +40,14 @@ import UIKit
/// }
///
/// func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
/// log.warn("App did receive memory warning.")
/// log.warning("App did receive memory warning.")
/// }
///
/// func applicationWillTerminate(_ application: UIApplication) {
/// log.warn("App will terminate.")
/// log.warning("App will terminate.")
/// }
/// }
open class ApplicationPluggableDelegate: UIResponder, UIApplicationDelegate, WindowDelegate {
open class ApplicationPluggableDelegate: UIResponder, UIApplicationDelegate {
public var window: UIWindow?

/// List of application plugins for binding to `AppDelegate` events
Expand Down Expand Up @@ -80,6 +81,14 @@ extension ApplicationPluggableDelegate {
$0 && $1.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

open func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// Ensure all delegates called even if condition fails early
//swiftlint:disable reduce_boolean
pluginInstances.reduce(false) {
$0 || $1.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
}
}

extension ApplicationPluggableDelegate {
Expand Down Expand Up @@ -131,10 +140,11 @@ extension ApplicationPluggableDelegate {
}
}

/// Conforming to an app module and added to `AppDelegate.application()` will trigger events.
/// Conforming to an app plugin and added to `AppDelegate.application()` will trigger events.
public protocol ApplicationPlugin {
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool

func applicationProtectedDataWillBecomeUnavailable(_ application: UIApplication)
func applicationProtectedDataDidBecomeAvailable(_ application: UIApplication)
Expand All @@ -148,6 +158,7 @@ public protocol ApplicationPlugin {
public extension ApplicationPlugin {
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { return true }
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { return true }
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { return false }

func applicationProtectedDataWillBecomeUnavailable(_ application: UIApplication) {}
func applicationProtectedDataDidBecomeAvailable(_ application: UIApplication) {}
Expand Down
Loading

0 comments on commit fd0a70b

Please sign in to comment.