Skip to content

gokselkoksal/Lightning

Repository files navigation

Lightning ⚑

Carthage CocoaPods CI Status Platform Language License

Lightning provides components to make Swift development easier.

If you're looking to migrate from an old version, see releases.

Components

Property Wrapper: @Stored πŸ“¦

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 to KeyValueStoreProtocol. 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 to Storable on any RawRepresentable type. No extra implementation needed.
  • Any Codable: Conform to StorableCodable on any Codable type. No extra implementation needed.
  • Any Storable: Conform to Storable protocol and provide custom implementation for your type.

Channel πŸ—Ό

Channel is now a part of Rasat!

StringFormatter πŸ–ŠοΈ

// - Perform 05308808080 -> 0 (530) 880 80 80

let phoneFormatter = StringFormatter(pattern: "# (###) ### ## ##")
let formattedNumber = phoneFormatter.format("05308808080") // Returns "0 (530) 880 80 80"

StringMask πŸ™ˆ

// - 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 βš›οΈ

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 ⏱️

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 & WeakArray πŸ—ƒοΈ

  • Weak is a wrapper to reference an object weakly.
  • WeakArray is an Array that references its elements weakly. (Similar to NSPointerArray.)

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()
}

ActivityState βŒ›

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()
  }
}

CollectionChange πŸ“±πŸ“²

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)
}

Extensions

Lightning provides extensions on known types with zap ⚑ prefix.

String+Helpers

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].

Dictionary+Helpers

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)]

Bundle+Helpers

Provides version string helpers.

// Version field:
bundle.zap_shortVersionString // 1.2.1

// Build field:
bundle.zap_versionString      // 345

Installation

Using CocoaPods

Add the following line to your Podfile:

pod 'Lightning'

Using Carthage

Add the following line to your Cartfile:

github "gokselkoksal/Lightning"

Manually

Drag and drop Sources folder to your project.

It's highly recommended to use a dependency manager like CocoaPods or Carthage.

License

Lightning is available under the MIT license.