Lightning provides components to make Swift development easier.
If you're looking to migrate from an old version, see releases.
Stored
is a property wrapper that read/write values using an internal key-value store.
Unlike widely used
@UserDefault
property wrapper,@Stored
can internally use any key-value store that conforms toKeyValueStoreProtocol
. There are two pre-defined key-value stores in Lightning:
UserDefaults
InMemoryKeyValueStore
: A key-value store that uses an internal dictionary to store data. Useful when unit-testing.
Definition:
final class UserPreferences {
@Stored var temperatureUnit: TemperatureUnit?
@Stored var weightUnit: WeightUnit
// Inject store into wrappers here:
init<S: KeyValueStoreProtocol>(store: S) where S.Key == String {
_temperatureUnit = Stored(key: "temperature-unit", store: store)
_weightUnit = Stored(key: "weight-unit", defaultValue: .grams, store: store)
}
Usage in app target:
let store = UserDefaults.standard
let preferences = UserPreferences(store: store)
preferences.temperatureUnit = .celsius
Usage in test target:
let store = InMemoryKeyValueStore<String>() // Internally a simple [String: Any] dictionary.
let preferences = UserPreferences(store: store)
preferences.temperatureUnit = .celsius
Supported types:
- Primitives:
Data
,String
,Date
,NSNumber
,Int
,UInt
,Double
,Float
,Bool
,URL
- Any
RawRepresentable
: Conform toStorable
on anyRawRepresentable
type. No extra implementation needed. - Any
Codable
: Conform toStorableCodable
on anyCodable
type. No extra implementation needed. - Any
Storable
: Conform toStorable
protocol and provide custom implementation for your type.
Channel is now a part of Rasat!
// - Perform 05308808080 -> 0 (530) 880 80 80
let phoneFormatter = StringFormatter(pattern: "# (###) ### ## ##")
let formattedNumber = phoneFormatter.format("05308808080") // Returns "0 (530) 880 80 80"
// - Perform 1111222233334444 -> ********33334444
let cardMask = StringMask(ranges: [NSRange(location: 0, length: 8)])
let cardMaskStorage = StringMaskStorage(mask: mask)
// 1. Pass it into the storage:
cardMaskStorage.original = "1111222233334444"
// 2. Read masked & unmasked value back:
let cardNo = cardMaskStorage.original // "1111222233334444"
let maskedCardNo = cardMaskStorage.masked // "********33334444"
Atomic
is a thread safe container for values.
var list = Atomic(["item1"])
// Get value:
let items = list.value
// Set value:
list.value = ["item1", "item2"]
// Read block:
list.read { items in
print(items)
}
// Write block:
list.write { items in
items.append(...)
}
TimerController
is a wrapper around Timer
, which makes it easy to implement countdowns.
let ticker = Ticker() // or MockTicker()
let timerController = TimerController(total: 60, interval: 1, ticker: ticker)
timerController.startTimer { state in
timerLabel.text = "\(state.remaining) seconds remaining..."
}
Weak
is a wrapper to reference an object weakly.WeakArray
is anArray
that references its elements weakly. (Similar toNSPointerArray
.)
Following example shows how it can be used for request cancelling.
var liveRequests = WeakArray<URLSessionTask>()
func viewDidLoad() {
super.viewDidLoad()
// Following async requests will be live until we get a response from server.
// Keep a weak reference to each to be able to cancel when necessary.
let offersRequest = viewModel.getOffers { ... }
liveRequests.appendWeak(offersRequest)
let favoritesRequest = viewModel.getFavorites { ... }
liveRequests.appendWeak(favoritesRequest)
}
func viewWillDisappear() {
super.viewWillDisappear()
liveRequests.elements.forEach { $0.cancel() }
liveRequests.removeAll()
}
Component to track live activities. Mostly used to show/hide loading view as in the following example.
var activityState = ActivityState() {
didSet {
guard activityState.isToggled else { return }
if activityState.isActive {
// Show loading view.
} else {
// Hide loading view.
}
}
}
func someProcess() {
activityState.add()
asyncCall1() {
// ...
activityState.add()
asyncCall2() {
// ...
activityState.remove()
}
activityState.remove()
}
}
public enum CollectionChange {
case reload
case update(IndexPathSetConvertible)
case insertion(IndexPathSetConvertible)
case deletion(IndexPathSetConvertible)
case move(from: IndexPathConvertible, to: IndexPathConvertible)
}
Enum to encapsulate change in any collection. Can be used to model UITableView
/UICollectionView
or any CollectionType
changes.
func addCustomer(_ customer: Customer) -> CollectionChange {
customers.insert(customer, at: 0)
return .insertion(0)
}
Lightning provides extensions on known types with zap
β‘ prefix.
let string = "Welcome"
// Int -> String.Index conversion:
let index1 = string.zap_index(1)
let eChar = string[index1] // "e"
let eChar = string.zap_character(at: 1) // "e"
// NSRange -> Range<String.Index> conversion:
let nsRange = NSRange(location: 0, length: 3)
let substring = string.zap_substring(with: nsRange) // "Wel"
let stringRange = string.zap_range(from: nsRange)
let substring = string.substring(with: stringRange) // "Wel"
// Range validation for NSRange -> Range<String.Index>:
let shortString = "Go"
let intersectedRange = shortString.zap_rangeIntersection(with: nsRange)
// `nsRange` [0, 2] is out of bounds for "Go". Intersection is [0, 1].
Introduces +
and +=
operators.
let dict1 = ["k1": "v1", "k2": "v2"]
let dict2 = ["k3": "v3"]
var dict3 = dict1 + dict2 // [(k1: v1), (k2: v2), (k3: v3)]
dict3 += ["k4": "v4"] // [(k1: v1), (k2: v2), (k3: v3), (k4: v4)]
dict3 += ["k4": "xx"] // [(k1: v1), (k2: v2), (k3: v3), (k4: xx)]
Provides version string helpers.
// Version field:
bundle.zap_shortVersionString // 1.2.1
// Build field:
bundle.zap_versionString // 345
Using CocoaPods
Add the following line to your Podfile
:
pod 'Lightning'
Using Carthage
Add the following line to your Cartfile
:
github "gokselkoksal/Lightning"
Drag and drop Sources
folder to your project.
It's highly recommended to use a dependency manager like CocoaPods
or Carthage
.
Lightning is available under the MIT license.