This is an SDK that provides some very useful helper properties and objects to make it much more easier to work with Swift and SwiftUI on your day-to-day coding.
This SDK is only available in Swift Package Manager. Please follow the steps below to add GQSwiftCoreSDK with Swift Package Manager.
- Select your project and navigate to
Package Dependencies
. - Click on the
+
button at the bottom-left of thePackages
section. - Paste https://github.com/geoo993/GQSwiftCoreSDK.git into the Search Bar.
- Press
Add Package
. - Xcode will download the package and prompt you to add the package in your project.
The following are the different properties available as extension of Swift value types.
Sometimes when subscripting in an Array, you might feel uncertain that the index used is within the bounds of the array.
This safe:
subscript allow you to return an optional value if a given index is out of bounds of the array.
guard let value = array[safe: index] else {
return
}
...
Sometimes you just want to know if an optional value is nil or not without unwrapping it.
var value: Int? = nil
...
if value.isNil {
...
} else {
...
}
For strings, you can even go further by checking if the string is nil or empty.
var value: String? = nil
...
if value.isNilOrEmpty {
...
} else {
...
}
Sometimes you want to quickly map a string to URL, this often occurs when you decoded a URL as string and then want to convert it back to a URL.
var imageUrl: URL?
...
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let urlString = try container.decode(String.self, forKey: .image)
imageUrl = urlString.toUrl
...
}
Sometimes you want to check if a string is not only empty but if its blank, without any tabs or whitespaces.
var value: String = " "
...
if value.isBlank {
...
} else {
...
}
When working with Booleans, there are much more pleasant ways of working with them that makes your code much easier to read.
Using Then
helps achieve a more declarative handling of side effects. Instead of nesting the effect in a branched if-else, it reads as a simple closure, removing the extra depth.
var isTodayMonday: Bool = true
...
isTodayMonday.then { runSideEffect() }
...
Similarly, we can isFalse
instead of !
to make working with Bool much easier to read.
var isTodayMonday: Bool = false
...
if isTodayMonday.isFalse {
...
}
...
The following are the some very useful objects that address some very common stumbling blocks when working with Swift and SwiftUI.
In our day-to-day tasks, we often have to instantiate or initialise objects with their values in multiple places and perhaps multiple times. This is very commone when working with UIViews.
With
allows you to combine the initialisation and injection of initial values of an object all at once.
extension UIView: With {}
...
class RedVieWController: UIViewController {
let container = UIView().with {
$0.backgroundColor = UIColor.red
$0.layer.cornerRadius = 12
$0.layer.borderWidth = 1
$0.layer.borderColor = UIColor.gray.cgColor
}
...
}
Sometimes when composing your View in SwiftUI, you might notice that its not easy to work with optional values.
Unwrap
makes it easy to build your Views with optionals using a ViewBuilder
that unwraps your optional and returns the value in the closure.
final class ViewModel: ObservableObject {
@Published var product: Product?
...
}
struct ContentView: View {
@StateObject var viewModel: ViewModel
var body: some View {
VStack {
Unwrap(viewModel.product) { product in
Text(product.name)
.font(.title3)
.fontWeight(.semibold)
}
...
}
}
}
It is very common that our app encounters many errors, the more errors at your disposal the harder it is to map or transfer those errors to relevant parts of your code.
AnyError
is a type erasure that makes it easy to work with mutliple implementation of errors, by wrapping them so they can be easily moved around your code.
enum APIError: Error {
case responseFailed
}
final class ProductRepository {
...
func products() -> AnyPublisher<Product, APIError> {
...
}
}
final class ViewModel: ObservableObject {
private var repository: ProductRepository
private var subscriptions: Set<AnyCancellable> = []
@Published var product: Result<Product, AnyError>
...
func getProducts() {
repository.products()
.receive(on: self.queue)
.mapError(AnyError.init)
.sink { completion in
switch completion {
case let .failure(error):
self.product = .failure(error)
default: break
}
} receiveValue: { value in
self.product = .success(value)
}
.store(in: &subscriptions)
}
}
Most of the time, we make long running tasks such as server calls or loading resources from cache and we will often run them in a background queue. Loading
is a perfect Type
to set on your properties that are awaiting the results of our long running tasks. It allows you to set the status of your property throughout the different stages of your long running task.
enum APIError: Error {
case responseFailed
}
final class ProductRepository {
...
func products() -> AnyPublisher<Product, APIError> {
...
}
}
final class ViewModel: ObservableObject {
private var repository: ProductRepository
private var subscriptions: Set<AnyCancellable> = []
@Published var product: Loading<Product> = .idle
...
func getProducts() {
product = .loading()
repository.products()
.receive(on: self.queue)
.mapError(AnyError.init)
.sink { completion in
switch completion {
case let .failure(error):
self.product = .error(error)
default: break
}
} receiveValue: { value in
self.product = .loaded(value)
}
.store(in: &subscriptions)
}
}
As Swift and SwiftUI evolves, there will be more and more properties that will emerge and turn out to be extremely usually as part of this toolkit, but for now I wish you happy coding. π