Astro is a library, built in swift, used to hold common utility methods.
Table of Contents generated with DocToc
- iOS 11+
- Xcode 10.1
To integrate Astro
into your Xcode project using Cocoapods, specify it in your Podfile
:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '11.0'
use_frameworks!
pod 'Astro'
Or if you don't want the whole enchilada then grab one of the subspecs:
pod 'Astro/Logging'
pod 'Astro/Security'
pod 'Astro/UI'
Log
is a structure that streamlines the printing of log messages.
Out of the box, you will be able to log error messages.
Log.error("I want to log an error message with \(something)")
However, if you want to see more information you can override the logging level as you wish. For example:
#if DEBUG
Log.level = .Debug
#else
Log.level = .Silent
#endif
Log.info("I want to log an info message with \(something)")
Log.debug("I want to log a debug message with \(somethingElse)")
Log.warning("I want to log a warning message with \(somethingOtherThanElse)")
You can also write a custom logger as long as it conforms to the Logger
protocol.
Log.logger = MyCustomLogger()
KeychainAccess
provides the app access to a device's Keychain store. Usage is fairly straightforward, as part of an account, you can place strings (or data) for a key into the Keychain and then recover those values later. This makes it a good way to securely store a specific user's password or tokens for reuse in the app. For more details on what else you can store, check out the KeychainAccessSpec.swift file.
// Instantiate the keychain access using a unique account identifier to house your key/values
let keychain = KeychainAccess(account: "[email protected]")
// Store a login token
var loginTokenID = "LoginToken"
let loginTokenValue = "SomeSuperSecretValueAboutACat"
keychain.putString(testKey, value: loginTokenID)
// And pull it back for later use
let loginToken = keychain.getString(loginTokenID)
NOTES:
- It is a simple keychain store library and doesn't include any fancy integration with iCloud or TouchID
Includes a UIColor extension for hex code (e.g. #FF0000
) support. You can now create your project's color palette in another class extension that brings all those pesky colors into one place and with names that are easy to understand:
private static let _FF9000 = UIColor(hexString: "#FF9000")!
public static func myApp_BrightOrangeColor() -> UIColor {
return _FF9000
}
In your app's implementation you you can then quickly make use of those colors:
let color = UIColor.myApp_BrightOrangeColor()
In many iOS apps, it is common to need a type identifier to instantiate views, register instances for reuse or dequeue cells. To assist with this and avoid having to define identifiers manually, a number of protocols are included in Astro/UI.
Provides a static identifier
value that defaults to the type name. UIViewController conforms to this automatically, and this can be used when instantiating from a storyboard.
class AstroViewController: UIViewController {}
let vc = UIStoryboard.main.instantiateView(ofType: AstroViewController.self)
ReusableView
has a reuseIdentifier
, which defaults to the type's identifier, or type name. This can be overridden if needed by having conforming types provide their own implementation. MKAnnotationView conforms to this automatically, and this can be used when instantiating with a MKMapView.
class AstroAnnotationView: MKAnnotationView {
// ...
}
mapView.registerView(ofType: AstroAnnotationView.self)
mapView.dequeueReusableAnnotationView(ofType: AstroAnnotationView.self, for: annotation)
Like ReusableView but for cell types 😄. There are currently no further requirements of types that conform to this protocol, but extensions on UITableView and UICollectionView require ReusableCells.
class AstroTableViewCell: UITableViewCell {
// ...
}
class AstroTableViewController: UITableViewController {
// ...
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeueReusableCell(ofType: AstroTableViewCell.self, for: indexPath)
}
// ...
}
NibLoadableView
has a nibName
, which should be the NIB filename and defaults to the type's name.
class AstroView: UIView, NibLoadableView {
// ...
}
class AstroViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let astroNibName = AstroView.nibName // "AstroView"
}
}
Why bother with this NibLoadableView
though, you ask? Watch how it combines with ReusableView
, and some nifty extensions to reduce more boilerplate...
As alluded to, there are UITableView
and UICollectionView
extensions that make use of ReusableCell
and NibLoadableView
for really easy cell registration and dequeueing.
The register method can take in a cell subclass that adheres to only ReusableCell
, or both ReusableCell
and NibLoadableView
. After the cell is registered, the provided dequeue method can be used in the necessary delegate method which allows you to stick with just using types to reference our views, and get back the specific view type we just dequeued:
class BookCell: UICollectionViewCell, NibLoadableView {
// ...
}
class BookListViewController: UIViewController, UICollectionViewDataSource {
@IBOutlet private weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.registerCell(ofType: BookCell.self)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(ofType: BookCell.self, for: indexPath)
return cell
}
}
Queue
provides a prettier interface for the most common needs of dispatching onto different GCD Queues. If you require something more powerful consider Async.
Queue.Background.execute {
// Do some work...
Queue.Main.executeAfter(delay: 1) {
// Back on main thread
}
}
Change<Value>
encapsulates a change between two values of the same type
Sometimes you need the old and new values of a property in order to perform efficient or pleasing changes elsewhere, like in your UI. This type makes that simpler by allowing you to pass a single value around. It's then possible to get sub-changes with the change[at: \.value]
subscript.
var model: ViewModel {
didSet {
let change = Change(old: oldValue, new: model)
updateUI(with: change)
}
}
func updateUI(with change: Change<Model>) {
title = change.new.title
updateSomeSubview(with: change[at: \.subviewState])
// ...
}
func updateSomeSubview(with change: Change<SubviewState>) {
guard change.isDifferent else { return }
// ...
}
As the library matures, more classes will be introduced to the project and it would be nice to keep it from becoming a mish-mash of things. One of the ways we intend to do this is to cluster the code in directories by functionality using pod subspecs for these modules. That way if a project just needs one or two things they can grab that subset easily.
So if you want to add some classes in, think about the existing modules and decide if it belongs with one or if it should have a new home. If you don't know then please ask.
Finally, if you have been tasked with helping maintain this library you can check out the CocoaPods Admin page for more details
Made with ❤ by Robots & Pencils (@robotsNpencils)
Astro is available under the MIT license. See the LICENSE file for more info.