I want to know how I can do the following:
var x = Observed<String>("hello")
label.text = x
Somewhere else x is updated:
x.value = "World"
Then label.text is automatically updated. Here is what I have for Observed so far
class Observed<T> {
typealias Observer = (T)->()
var observer: Observer?
var value: T {
didSet {
if let observer = observer {
observer(value)
}
}
}
init(_ v: T) {
value = v
}
}
I believe this is sort of what RxSwift, etc. do, but that's overkill for what I'm doing
You should probably just use a didSet in this scenario.
class YourClass {
var x: String = "" { didSet { someVC.someLabel.text = x } }
}
There is no "native" data binding in iOS as of now.
EDIT: of course there is KVO, thanks #matt for the heads-up :)
Related
From within a property wrapper in Swift, can you someone refer back to the instance of the class or struck that owns the property being wrapped? Using self doesn't obviously work, nor does super.
I tried to pass in self to the property wrapper's init() but that doesn't work either because self on Configuration is not yet defined when #propertywrapper is evaluated.
My use case is in a class for managing a large number of settings or configurations. If any property is changed, I just want to notify interested parties that something changed. They don't really need to know which value just, so use something like KVO or a Publisher for each property isn't really necessary.
A property wrapper looks ideal, but I can't figure out how to pass in some sort of reference to the owning instance that the wrapper can call back to.
References:
SE-0258
enum PropertyIdentifier {
case backgroundColor
case textColor
}
#propertyWrapper
struct Recorded<T> {
let identifier:PropertyIdentifier
var _value: T
init(_ identifier:PropertyIdentifier, defaultValue: T) {
self.identifier = identifier
self._value = defaultValue
}
var value: T {
get { _value }
set {
_value = newValue
// How to callback to Configuration.propertyWasSet()?
//
// [self/super/...].propertyWasSet(identifier)
}
}
}
struct Configuration {
#Recorded(.backgroundColor, defaultValue:NSColor.white)
var backgroundColor:NSColor
#Recorded(.textColor, defaultValue:NSColor.black)
var textColor:NSColor
func propertyWasSet(_ identifier:PropertyIdentifier) {
// Do something...
}
}
The answer is no, it's not possible with the current specification.
I wanted to do something similar. The best I could come up with was to use reflection in a function at the end of init(...). At least this way you can annotate your types and only add a single function call in init().
fileprivate protocol BindableObjectPropertySettable {
var didSet: () -> Void { get set }
}
#propertyDelegate
class BindableObjectProperty<T>: BindableObjectPropertySettable {
var value: T {
didSet {
self.didSet()
}
}
var didSet: () -> Void = { }
init(initialValue: T) {
self.value = initialValue
}
}
extension BindableObject {
// Call this at the end of init() after calling super
func bindProperties(_ didSet: #escaping () -> Void) {
let mirror = Mirror(reflecting: self)
for child in mirror.children {
if var child = child.value as? BindableObjectPropertySettable {
child.didSet = didSet
}
}
}
}
You cannot do this out of the box currently.
However, the proposal you refer to discusses this as a future direction in the latest version:
https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#referencing-the-enclosing-self-in-a-wrapper-type
For now, you would be able to use a projectedValue to assign self to.
You could then use that to trigger some action after setting the wrappedValue.
As an example:
import Foundation
#propertyWrapper
class Wrapper {
let name : String
var value = 0
weak var owner : Owner?
init(_ name: String) {
self.name = name
}
var wrappedValue : Int {
get { value }
set {
value = 0
owner?.wrapperDidSet(name: name)
}
}
var projectedValue : Wrapper {
self
}
}
class Owner {
#Wrapper("a") var a : Int
#Wrapper("b") var b : Int
init() {
$a.owner = self
$b.owner = self
}
func wrapperDidSet(name: String) {
print("WrapperDidSet(\(name))")
}
}
var owner = Owner()
owner.a = 4 // Prints: WrapperDidSet(a)
My experiments based on : https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#referencing-the-enclosing-self-in-a-wrapper-type
protocol Observer: AnyObject {
func observableValueDidChange<T>(newValue: T)
}
#propertyWrapper
public struct Observable<T: Equatable> {
public var stored: T
weak var observer: Observer?
init(wrappedValue: T, observer: Observer?) {
self.stored = wrappedValue
}
public var wrappedValue: T {
get { return stored }
set {
if newValue != stored {
observer?.observableValueDidChange(newValue: newValue)
}
stored = newValue
}
}
}
class testClass: Observer {
#Observable(observer: nil) var some: Int = 2
func observableValueDidChange<T>(newValue: T) {
print("lol")
}
init(){
_some.observer = self
}
}
let a = testClass()
a.some = 4
a.some = 6
The answer is yes! See this answer
Example code for calling ObservableObject publisher with a UserDefaults wrapper:
import Combine
import Foundation
class LocalSettings: ObservableObject {
static var shared = LocalSettings()
#Setting(key: "TabSelection")
var tabSelection: Int = 0
}
#propertyWrapper
struct Setting<T> {
private let key: String
private let defaultValue: T
init(wrappedValue value: T, key: String) {
self.key = key
self.defaultValue = value
}
var wrappedValue: T {
get {
UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
public static subscript<EnclosingSelf: ObservableObject>(
_enclosingInstance object: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, T>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Setting<T>>
) -> T {
get {
return object[keyPath: storageKeyPath].wrappedValue
}
set {
(object.objectWillChange as? ObservableObjectPublisher)?.send()
UserDefaults.standard.set(newValue, forKey: object[keyPath: storageKeyPath].key)
}
}
}
struct Kitchen // Coordinator
{
var foodItems = [FoodItem] () // Collection of FoodItem objects.
var wastedItems = [WastedItem]() // Collection of WastedItem
var totalSpend = Double()
var currentSpend = Double()
var weeklyHouseholdFoodBudget = Double()
var wastageCost = Double()
init(aTotal:Double,aCurrent:Double,aBudget:Double,aWastage:Double)
{
self.totalSpend = aTotal
self.currentSpend = aCurrent
self.weeklyHouseholdFoodBudget = aBudget
self.wastageCost = aWastage
}
struct FoodItem : Equatable
{
var itemName = String()
var itemQuantity = Double()
var dateOfUse = String()
var unitOfMeasurement = String()
init(aName:String,aQuantity:Double,aDateOfUse:String,aUnit:String)
{
self.itemName = aName
self.itemQuantity = aQuantity
self.dateOfUse = aDateOfUse
self.unitOfMeasurement = aUnit
}
mutating func listFoodItems()
{
for item in foodItems
{
print("Item Name:", item.getName(),",",
"Qunatity:",item.getItemQuantity(),",",
"Unit:",item.getUnitOfMeasurement(),",",
"Date of use:",item.getDateOfUse())
}
}
mutating func removeFoodItem(aFood:FoodItem)
{
if let anIndex = foodItems.index(of: aFood)
{
foodItems.remove(at: anIndex)
print(aFood.getName(),"Has been removed")
}
}
mutating func useFood(aFoodItem:inout FoodItem,inputQty:Double)
{
if (aFoodItem.getItemQuantity()) - (inputQty) <= 0
{
self.removeFoodItem(aFood: aFoodItem)
}
else
{
aFoodItem.useFoodItem(aQty: inputQty)
}
}
***Updated****
The issue I am having is when I use a func listFoodItems() the updated attribute itemQuantity does not change. I would like to know how to update the collection so when I call the func listFoodItems() it displays value changes.
The removal is ok, when the func runs the collection removes the object.
The issue must be because I am using for item in foodItems to display, I need to reload it with updated values before I do this?
Thank you for any assistance.
Property observers are used on properties.
The simplest example of a property observer.
class Foo {
var bar: String = "Some String" {
didSet {
print("did set value", bar)
}
}
}
Now if you want to display some changes, you will need your own code in didSet method in order to do that. For example call reloadData().
How do you observe additions/deletions to an array of structs?
If it were an array of classes, you could make the array dynamic and use KVO.
With structs you run into problems with #objc or #objcMembers in iOS 11
Do you control the definition of the array? If so, you can add a didSet observer:
var array: [MyStruct] {
didSet {
// do something with array and/or oldValue
}
}
This will be called every time the array or one of its elements is mutated.
Ole is right. FWIW Here's how I tested it
struct MyStruct : CustomStringConvertible {
var thing = "thing"
init(_ s:String) {
print("struct \(#function)")
thing = s
}
var description:String {
get {
return "\(thing)"
}
}
}
class Foo {
var a:[MyStruct] {
didSet {
print("didSet: a was set \(a)")
}
}
init() {
print("class \(#function)")
a = [MyStruct("from Init")]
}
func blammo() {
print("\(#function)")
print("adding")
a.append(MyStruct("Added"))
print("new a \(a)")
}
}
let foo = Foo()
foo.blammo()
Let's assume that myVar has same functionality in every implementation of view. I am trying to figure out how to declare/expose some kind of set-only property instead of assigning them n-times (with every new view created), but nothing comes to my head. How could I refactor into one line & one time assignment?
var myVar: (()-> Void)?
private func callBack() {
someClass.view1.myVar = self.myVar
someClass.view2.myVar = self.myVar
someClass.view3.myVar = self.myVar
}
// MARK: - someClass pseudocode
someClass: class {
let view1: CustomView: = CustomView
let view2: CustomView: = CustomView
let view3: CustomView: = CustomView
}
// MARK: - customView pseudocode
class CustomView: UIView {
var myVar: (()-> Void)?
}
something like this, but having all CustomViews in an array is good idea and could be implemented here as well
var a: (() -> Void)?
class CustomView: UIView {
var myVar: (() -> Void)?
}
class SomeClass {
let view1 = CustomView()
let view2 = CustomView()
let view3 = CustomView()
var myVar: (() -> Void)? {
set {
self.view2.myVar = newValue
self.view1.myVar = newValue
self.view3.myVar = newValue
}
get {
return self.myVar
}
}
}
let b = SomeClass()
b.myVar = ({print(3)})
b.view1.myVar!()
Is this what you are trying to do?
[someClass.view1, someClass.view2, someClass.view3].forEach { $0.myVar = self.myVar }
This is how I tend to deal with these issues:
class OtherClass {
var text: String?
init(text: String?) {
self.text = text;
}
}
class TestClass {
var thing1: OtherClass?
var thing2: OtherClass?
var thing3: OtherClass?
var allTheThings: [OtherClass?] { return [thing1, thing2, thing3]}
var ownText: String? {
didSet {
allTheThings.forEach { $0?.text = ownText }
}
}
}
Depending on how much you expect things to change you could make the array property a constant you set in your init rather than a computed property.
If you want to get fancy you could also do something like this for setting:
private var allTheThings: [OtherClass?] {
get {
return [thing1, thing2, thing3]
}
set {
guard newValue.count == 3 else {
//probably should put an assertion in here
return
}
thing1 = newValue[0]
thing2 = newValue[1]
thing3 = newValue[2]
}
}
init() {
self.allTheThings = Array(repeating: OtherClass(text: "Test"), count: 3)
}
I have a property
public lazy var points: [(CGFloat,CGFloat,CGFloat)] = {
var pointsT = [(CGFloat,CGFloat,CGFloat)]()
let height = 100.0
for _ in 1...10 {
pointsT.append((someValue,someValue,100.0))
}
return pointsT
}()
And i want to add a didSet method, is it possible?
Short answer: no.
Try out this simple example in some class or method of yours:
lazy var myLazyVar: Int = {
return 1
} () {
willSet {
print("About to set lazy var!")
}
}
This gives you the following compile time error:
Lazy properties may not have observers.
With regard to the let statement in the other answer: lazy variable are not necessary just "let constants with delayed initialisation". Consider the following example:
struct MyStruct {
var myInt = 1
mutating func increaseMyInt() {
myInt += 1
}
lazy var myLazyVar: Int = {
return self.myInt
} ()
}
var a = MyStruct()
print(a.myLazyVar) // 1
a.increaseMyInt()
print(a.myLazyVar) // 1: "initialiser" only called once, OK
a.myLazyVar += 1
print(a.myLazyVar) // 2: however we can still mutate the value
// directly if we so wishes
The short answer is as other have said is "no" but there is a way to get the effect using a private lazy var and computed var.
private lazy var _username: String? = {
return loadUsername()
}()
var username: String? {
set {
// Do willSet stuff in here
if newValue != _username {
saveUsername(newValue)
}
// Don't forget to set the internal variable
_username = newValue
// Do didSet stuff here
// ...
}
get {
return _username
}
}
No
points is a constant, you cannot set anything to it. The only difference to a let constant is that it is (potentially) initialized later.
This answer provides a little more information as to why you use var instead of let in the lazy case.
Edit: to make the answer not look to empty
Take a look at this blog post where the author raises a few valid points regarding why observing lazy vars might not yet be supported. What should oldValue be in the observer? nil? That would not be a good idea in your non-optional case.
Yes, Started by version Swift 5.3 Property observers can now be attached to lazy properties.
class C {
lazy var property: Int = 0 {
willSet { print("willSet called!") } // Okay
didSet { print("didSet called!") } // Okay
}
}
or
class C {
lazy var property: Int = { return 0 }() {
willSet { print("willSet called!") } // Okay
didSet { print("didSet called!") } // Okay
}
}
Please take a look at the links below for more:
https://www.swiftbysundell.com/tips/lazy-property-observers/
https://github.com/apple/swift/blob/main/CHANGELOG.md