CocoaPods trunk is moving to be read-only. Read more on the blog, there are 13 months to go.
| TestsTested | ✓ |
| LangLanguage | SwiftSwift |
| License | MIT |
| ReleasedLast Release | Jun 2016 |
| SPMSupports SPM | ✗ |
Maintained by Sam Williams.
MagneticFields is a library for adding fields to your model objects. It’ll give you:
MagneticFields is available through CocoaPods. To install it, add the following line to your Podfile:
pod "MagneticFields"To run the example project, clone the repo, and run pod install from the Example directory first.
class Person {
let age = Field<Int>()
}
person.age.value = 10The basic field type is Field, a generic class whose type parameter is its value type. For multivalued fields, there is ArrayField, which wraps another field that describes the single-valued type.
let tag = Field<String>(name: "Tag")
let tags = ArrayField(Field<String>(), name: "Tag")The inner field is responsible for validations, transformations, etc.. The ArrayField owns top-level attributes like name, key, etc. – but for convenience, it will copy them from the inner field at initialization.
The unary prefix operator * is provided to wrap a Field in an ArrayField. So you can also write the above declaration like this:
let tags = *Field<String>(name: "Tag")Simple closure validations:
let age = Field<Int>().require { $0 > 0 }Rules can be chained, too, implying an AND. Order is not important.
let age = Field<Int>().require { $0 > 0 }.require { $0 % 2 == 0 }By default, nil values will be considered valid. To change that for a given rule, pass allowNil: false to require.
To validate a field value, either call field.valid (returning a Bool) or field.validate(), which returns a ValidationState enum:
public enum ValidationState:Equatable {
case Unknown
case Invalid([String])
case Valid
}The associated value of the .Invalid case is a list of error messages (e.g., ["must be greater than 0", "is required"]).
Fields will automatically have the following timestamps:
updatedAt: the last time any value was setchangedAt: the last time a new value was set (compared using ==)This library includes the Observer and Observable protocols for generic, type-safe change observation. Fields implement both protocols.
An Observable can have any number of registered Observer objects. The --> operator is a shortcut for the addObserver method (<-- works the same, only with its arguments swapped). Observation events are triggered once when the observer is added, and after that whenever a field value is set.
An observer can be added if it implements the Observer protocol, which has a valueChanged(observable, value: value) method.
field --> observerOr, a closure can be provided. In place of an observer object, an owner is used only to identify each closure; each owner can only have one associated closure.
field --> owner { value in
print(value)
}We can still register a closure even if no observer is given. This is effectively registering the closure with a null observer. There can only be one of these at a time.
age --> { value in
print("Age was changed to \(value)")
}Since Field itself implements both Observable and Observer, the --> operator can be used to create a link between two field values.
sourceField --> destinationFieldThis will set the value of destinationField to that of sourceField immediately, and again whenever sourceField’s value changes.
The <--> operator is a shortcut for <-- followed by --> (and can only be used between two Fields).
field1 <--> field2Since <-- is called first, both fields will initially have the value of field2.
Fields and Observables have a strongly typed value property, which must match an Observer’s associated type (ValueType) or the closure’s parameter.
So this will fail to compile:
let name = Field<String>()
let age = Field<Int>()
name --> ageUnregistering observers is done with the removeObserver method, or the -/-> operator. All observers can be removed with removeAllObservers().
It can be useful to distinguish between a value that’s nil because hasn’t been loaded yet (e.g., from an API), and one that is known to be nil. For this, fields provide the state property, whose values are in the LoadState enum:
public enum LoadState {
case NotSet
case Set
case Loading
case Error
}All fields are initially in the .NotSet state, but automatically become .Set when their value is set to anything.
The .Loading state can be useful when the process of loading takes time. You might decide to show a spinner in the UI while making an API request, for example.