How to observe a change in a class's property from another class - ios

I've got a question on property observers. There's some example code below. What I want is for the property Analysis.hasChanged to be updated to true if a.value is changed. Is there a way I can do this?
class Number {
var value: Double
init(numberValue: Double) {
self.value = NumberValue
}
}
class Analysis {
var a: Number
var hasChanged = false
init(inputNumber: Number) {
self.a = inputNumber
}
}
testNumber = Number(numberValue: 4)
testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged) // will still print "false", but I want it to print "true"
In the end, I want the user to be able to be notified if any of their analyses use numbers that have been changed so that they can update the results of the analyses if they choose.

You can use the built-in property observers provided by Swift.
Every time you set a new value, the didSet will be called. You just need to attach the closure, wrapping the desired behaviour, to the Number class
class Number {
var valueDidChangeClosure: (()->())?
var value: Double {
didSet {
//won't call the valueDidChangeClosure
//if the value was changed from 10 to 10 for example..
if oldValue != value {
valueDidChangeClosure?()
}
}
}
init(numberValue: Double) {
self.value = numberValue
}
}
class Analysis {
var a: Number
var hasChanged = false
init(inputNumber: Number) {
self.a = inputNumber
self.a.valueDidChangeClosure = {
self.hasChanged = true
}
}
}
let testNumber = Number(numberValue: 4)
let testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged) // will print "true"

I would do something like this, I apologize in advance if I have some syntax wrong (I usually use C/C++, think of this as more psudo code since you'd have to have a way to copy Number classes, etc.).
class Number {
var value: Double
init(numberValue: Double) {
self.value = NumberValue
}
}
class Analysis {
var a: Number
var _a: Number
bool hasChanged() {
if (a != _a) {
_a = a
return true;
}
return false;
}
init(inputNumber: Number) {
self.a = inputNumber
self._a = self.a
}
}
testNumber = Number(numberValue: 4)
testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged()) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged()) // will still print "false", but I want it to print "true"
In the end, I want the user to be able to be notified if any of their analyses use numbers that have been changed so that they can update the results of the analyses if they choose.
I don't know if this really addresses that question, I based my answer off of the code you provided. So there may be additional functionality if you want there to be some triggering method (instead of calling .hasChanged()).

Comparing doubles (and any other floating point type) with '=' or '!=' is not a good idea.
Use epsilon function instead.
Details: jessesquires.com/blog/floating-point-swift-ulp-and-epsilon/

Related

Using reduce to change struct in Swift

I'm trying to understand how to change a struct (or class?) in an array by using reduce. Creating 4 countdown timers, on tap pause the current timer and start the next. So I tried something like this:
var timer1 = CountdownTimer()
// var timer2 = CountdownTimer() etc.
.onTapGesture(perform: {
var timers: [CountdownTimer] = [timer1, timer2, timer3, timer4]
var last = timers.reduce (false) {
(setActive: Bool, nextValue: CountdownTimer) -> Bool in
if (nextValue.isActive) {
nextValue.isActive = false;
return true
} else {
return false
}
}
if (last) {
var timer = timers[0]
timer.isActive = true
}
})
############# CountdownTimer is a struct ######
struct CountdownTimer {
var timeRemaining: CGFloat = 1000
var isActive: Bool = false
}
This does not work, two errors I'm seeing
the timers in the array are copies of the timers, not the actual timer so changing them doesn't actually change the timers being displayed on screen.
nextValue (i.e. the next timer) can't be changed because it's a let variable in the reduce declaration. I don't know how to change this (or if it's even relevant because presumably it's a copy of the copy of the timer and not the one I actually want to change).
Am I approaching this in a way thats idiomatically wrong for Swift? How should I be changing the original timers?
I agree with Paul about the fact that this should likely all be pulled out into an observable model object. I'd make that model hold an arbitrary list of timers, and the index of the currently active timer. (I'll give an example of that at the end.)
But it's still worth exploring how you would make this work.
First, SwiftUI Views are not the actual view on the screen like in UIKit. They are descriptions of the view on the screen. They're data. They can be copied and destroyed at any time. So they're readonly objects. The way you keep track of their writable state is through #State properties (or similar things like #Binding, #StateObject, #ObservedObject and the like). So your properties need to be marked #State.
#State var timer1 = CountdownTimer()
#State var timer2 = CountdownTimer()
#State var timer3 = CountdownTimer()
#State var timer4 = CountdownTimer()
As you've discovered, this kind of code doesn't work:
var timer = timer1
timer.isActive = true
That makes a copy of timer1 and modifies the copy. Instead, you want WriteableKeyPath to access the property itself. For example:
let timer = \Self.timer1 // Note capital Self
self[keyPath: timer].isActive = true
Finally, reduce is the wrong tool for this. The point of reduce is to reduce a sequence to a single value. It should never have side-effects like modifying the values. Instead, you just want to find the right elements, and then change them.
To do that, it would be nice to be able to easily track "this element and the next one, and the last element is followed by the first." That seems very complicated, but it's surprisingly simple if you include Swift Algorithms. That gives cycled(), which returns a Sequence that repeats its input forever. Why is that useful? Because then you can do this:
zip(timers, timers.cycled().dropFirst())
This returns
(value1, value2)
(value2, value3)
(value3, value4)
(value4, value1)
Perfect. With that I can fetch the first active timer (keypath) and its successor, and update them:
let timers = [\Self.timer1, \.timer2, \.timer3, \.timer4]
if let (current, next) = zip(timers, timers.cycled().dropFirst())
.first(where: { self[keyPath: $0.0].isActive })
{
self[keyPath: current].isActive = false
self[keyPath: next].isActive = true
}
That said, I wouldn't do that. There are subtle requirements here that should be captured in a type. In particular, you have this assumption that there is only one active timer, but nothing enforces that. If that's what you mean, you should make a type that says so. For example:
class TimerBank: ObservableObject {
#Published private(set) var timers: [CGFloat] = []
#Published private(set) var active: Int?
var count: Int { timers.count }
init(timers: [CGFloat]) {
self.timers = timers
self.active = timers.startIndex
}
func addTimer(timeRemaining: CGFloat = 1000) {
timers.append(timeRemaining)
}
func start(index: Int? = nil) {
if let index = index {
active = index
} else {
active = timers.startIndex
}
}
func stop() {
active = nil
}
func cycle() {
if let currentActive = active {
active = (currentActive + 1) % timers.count
print("active = \(active)")
} else {
active = timers.startIndex
print("(init) active = \(active)")
}
}
}
With this, timerBank.cycle() replaces your reduce.
By using the modulus operator ( % ) on Index, we could cycle through last to first without zipping.
let timers = [\Self.timer1, \.timer2, \.timer3, \.timer4]
if let onIndex = timers.firstIndex(where: { self[keyPath: $0].isActive }) {
self[keyPath: timers[onIndex]].isActive = false
let nextIndex = (onIndex + 1) % 4 // instead of 4, could use timers.count
self[keyPath: timers[nextIndex]].isActive = true
}

Swift sort array by Bool value and TimeStamp

I have an array of messages that I want to sort by whether they are unread or not, and their time stamp. These messages will be displayed in a TableView.
All unread messages should be displayed at the top, and then sorted by their timestamp.
Below is what I have right now; but what seems to be happening is that the items are displayed only by time stamp, without sorting them by unread at the top and then unread messages after.
let channelsSortedByUnreadFirst = dataStore.messages.sorted { $0.isUnread == true && $1.isUnread == false }
let timeSortedItems = channelsSortedByUnreadFirst.sorted(by: { $0.timeStamp > $1.timeStamp })
messagesTableViewSection.items = timeSortedItems
How do I sort these messages by both isUnread and timeStamp?
Bool does not conform to Comparable. What you need is to extend Bool type and convert its value to a type that conforms to Comparable like integer 0 or 1 and sort it using its value:
extension Bool {
var value: Int { self ? 1 : 0 }
}
true.value // 1
false.value // 0
Now you can use the boolean value when sorting your collection:
let channelsSorted = dataStore.messages.sorted { ($0.isUnread.value, $0.timeStamp) > ($1.isUnread.value, $1.timeStamp) }
Another way using Swift Algorithms stablePartition(by:):
import Algorithms
var messages = dataStore.messages
messages.sort { $0.timeStamp > $1.timeStamp }
let firstReadIndex = messages.stablePartition { !$0.isUnread }
Another Way that might be more intuitive but uses multiple passes.
print(messages.filter(\.isUnread).sorted { $0.timeStamp > $1.timeStamp } + messages.filter { !$0.isUnread })

Objects of same class have the same properties even though declared differently when pushing segue

I've been stuck trying to fix an issue in my application that prevents me from getting my desired result.
When pushing segue to the next view controller, I passed two objects of the same class to var original:[Person] = [] and var edited:[Person] = []
if (segue.identifier == "showSummary") {
let vc = segue.destination as! SummaryViewController
vc.original = myObject
vc.edited = Bill.calculateBill(myObject, switchServiceCharge.isOn, switchGST.isOn)
vc.gst = (switchServiceCharge.isOn, switchGST.isOn)
}
Before calculateBill, the original value of a property inside is 11.
The calculateBill is a class function in another class Bill:
class func calculateBill(_ bill: [Person],_ svcCharge: Bool,_ GST: Bool) -> [Person] {
var mutableBill = bill
for i in 0 ..< mutableBill.count {
for j in 0 ..< mutableBill[i].items.count {
let tax = Bill.getGSTForIndividual(mutableBill[i].items[j].itemPrice, svcCharge, GST)
mutableBill[i].items[j].itemPrice += tax
}
}
return mutableBill
}
class func getGSTForIndividual(_ individualAmt: Decimal,_ svcCharge: Bool,_ GST: Bool) -> Decimal {
var taxCost : Decimal = 0.00
let SERVICE_CHARGE = Bill.getServiceCharge() //0.10
let GOODS_SERVICE_TAX = Bill.getGST() //0.07
if (svcCharge && GST) {
taxCost = individualAmt * SERVICE_CHARGE
taxCost = taxCost + ((individualAmt + taxCost) * GOODS_SERVICE_TAX)
}
else if (!svcCharge && GST) {
taxCost = individualAmt * GOODS_SERVICE_TAX
}
else if (svcCharge && !GST) {
taxCost = individualAmt * SERVICE_CHARGE
}
else {
taxCost = 0.00
}
return taxCost
}
When I did a print() to test whether the properties inside are different, they both yield the same results somehow...
print(original[0].items[0].itemPrice) //12.947000000000000123904, originally 11
print(edited[0].items[0].itemPrice) //12.947000000000000123904
What exactly is going on and why do both the objects have the same properties even though I have declared them differently?
I'm guessing that Person is probably declared as a class, not a struct. This means that it's a reference type, and when you:
var mutableBill = bill
you're not actually making a copy of bill, but rather another reference to the same object. So, when you change the properties on mutableBill, you also change the properties on bill, since they both point to the same object.
The solution is to make an actual copy of bill. The two options are either to declare Person as a struct, in which case you will always get a copy, or else to make an initializer on Person that takes another Person and initializes itself using the passed-in Person's properties:
init(person: Person) { ... }

Find Object with Property in Array

is there a possibility to get an object from an array with an specific property? Or do i need to loop trough all objects in my array and check if an property is the specific i was looking for?
edit: Thanks for given me into the correct direction, but i have a problem to convert this.
// edit again: A ok, and if there is only one specific result? Is this also a possible method do to that?
let imageUUID = sender.imageUUID
let questionImageObjects = self.formImages[currentSelectedQuestion.qIndex] as [Images]!
// this is working
//var imageObject:Images!
/*
for (index, image) in enumerate(questionImageObjects) {
if(image.imageUUID == imageUUID) {
imageObject = image
}
}
*/
// this is not working - NSArray is not a subtype of Images- so what if there is only 1 possible result?
var imageObject = questionImageObjects.filter( { return $0.imageUUID == imageUUID } )
// this is not working - NSArray is not a subtype of Images- so what if there is only 1 possible result?
You have no way to prove at compile-time that there is only one possible result on an array. What you're actually asking for is the first matching result. The easiest (though not the fastest) is to just take the first element of the result of filter:
let imageObject = questionImageObjects.filter{ $0.imageUUID == imageUUID }.first
imageObject will now be an optional of course, since it's possible that nothing matches.
If searching the whole array is time consuming, of course you can easily create a firstMatching function that will return the (optional) first element matching the closure, but for short arrays this is fine and simple.
As charles notes, in Swift 3 this is built in:
questionImageObjects.first(where: { $0.imageUUID == imageUUID })
Edit 2016-05-05: Swift 3 will include first(where:).
In Swift 2, you can use indexOf to find the index of the first array element that matches a predicate.
let index = questionImageObjects.indexOf({$0.imageUUID == imageUUID})
This is bit faster compared to filter since it will stop after the first match. (Alternatively, you could use a lazy sequence.)
However, it's a bit annoying that you can only get the index and not the object itself. I use the following extension for convenience:
extension CollectionType {
func find(#noescape predicate: (Self.Generator.Element) throws -> Bool) rethrows -> Self.Generator.Element? {
return try indexOf(predicate).map({self[$0]})
}
}
Then the following works:
questionImageObjects.find({$0.imageUUID == imageUUID})
Yes, you can use the filter method which takes a closure where you can set your logical expression.
Example:
struct User {
var firstName: String?
var lastName: String?
}
let users = [User(firstName: "John", lastName: "Doe"), User(firstName: "Bill", lastName: "Clinton"), User(firstName: "John", lastName: "Travolta")];
let johns = users.filter( { return $0.firstName == "John" } )
Note that filter returns an array containing all items satisfying the logical expression.
More info in the Library Reference
Here is a working example in Swift 5
class Point{
var x:Int
var y:Int
init(x:Int, y:Int){
self.x = x
self.y = y
}
}
var p1 = Point(x:1, y:2)
var p2 = Point(x:2, y:3)
var p3 = Point(x:1, y:4)
var points = [p1, p2, p3]
// Find the first object with given property
// In this case, firstMatchingPoint becomes p1
let firstMatchingPoint = points.first{$0.x == 1}
// Find all objects with given property
// In this case, allMatchingPoints becomes [p1, p3]
let allMatchingPoints = points.filter{$0.x == 1}
Reference:
Trailing Closure
Here is other way to fetch particular object by using object property to search an object in array.
if arrayTicketsListing.contains({ $0.status_id == "2" }) {
let ticketStatusObj: TicketsStatusList = arrayTicketsListing[arrayTicketsListing.indexOf({ $0.status_id == "2" })!]
print(ticketStatusObj.status_name)
}
Whereas, my arrayTicketsListing is [TicketsStatusList] contains objects of TicketsStatusList class.
// TicketsStatusList class
class TicketsStatusList {
internal var status_id: String
internal var status_name: String
init(){
status_id = ""
status_name = ""
}
}

Immutable array on a var?

I am getting the error:
Immutable value of type 'Array Character>' only has mutating members of name removeAtIndex()
The array should have contents because that removeAtIndex line is in a loop who's condition is if the count > 1
func evaluatePostFix(expression:Array<Character>) -> Character
{
var stack:Array<Character> = []
var count = -1 // Start at -1 to make up for 0 indexing
if expression.count == 0 {
return "X"
}
while expression.count > 1 {
if expression.count == 1 {
let answer = expression[0]
return answer
}
var expressionTokenAsString:String = String(expression[0])
if let number = expressionTokenAsString.toInt() {
stack.append(expression[0])
expression.removeAtIndex(0)
count++
} else { // Capture token, remove lefthand and righthand, solve, push result
var token = expression(count + 1)
var rightHand = stack(count)
var leftHand = stack(count - 1)
stack.removeAtIndex(count)
stack.removeAtIndex(count - 1)
stack.append(evaluateSubExpression(leftHand, rightHand, token))
}
}
}
Anyone have any idea as to why this is? Thanks!
Because all function parameters are implicitly passed by value as "let", and hence are constant within the function, no matter what they were outside the function.
To modify the value within the function (which won't affect the value on return), you can explicitly use var:
func evaluatePostFix(var expression:Array<Character>) -> Character {
...
}

Resources