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

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

Related

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

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/

Find an item and change value in custom object array - Swift

I have this class
class InboxInterests {
var title = ""
var eventID = 0
var count = ""
var added = 0
init(title : String, eventID : NSInteger, count: String, added : NSInteger) {
self.title = title
self.eventID = eventID
self.count = count
self.added = added
}
}
And i use it like this
var array: [InboxInterests] = [InboxInterests]()
Add item
let post = InboxInterests(title: "test",eventID : 1, count: "test", added: 0)
self.array.append(post)
I want to find the index by eventID key and change the value of added key in the same index
How is that possible?
For me, the above answer did not work. So, what I did was first find the index of the object that I want to replace then using the index replace it with the new value
if let row = self.upcoming.index(where: {$0.eventID == id}) {
array[row] = newValue
}
In Swift 5.0:
if let row = self.upcoming.firstIndex(where: {$0.eventID == id}) {
array[row] = newValue
}
Since you are using a class, use filter and first to find the value:
array.filter({$0.eventID == id}).first?.added = value
In this you:
filter the array down to elements that match the event ID
pick the first result, if any
then set the value
This works since classes are pass by reference. When you edit the return value from array.filter({$0.eventID == id}).first?, you edit the underlying value. You'll need to see the answers below if you are using a struct
EDIT: In Swift 3 you can save yourself a couple of characters
array.first({$0.eventID == id})?.added = value
EDIT: Swift 4.2:
array.first(where: { $0.eventID == id })?.added = value
array.filter {$0.eventID == id}.first?.added = value
The filter operator is not the best in this case, it works for some of you because classes are passed by reference.
Explanation: (You can copy the following code in a playground if you want to verify it).
class Book {
let id: Int
var title = "default"
init (id: Int) {
self.id = id
}
}
var arrayBook = [Book]()
arrayBook.append(Book(id: 0))
arrayBook.append(Book(id:1))
arrayBook.forEach { book in
print(book.title)
}
arrayBook.filter{ $0.id == 1 }.first?.title = "modified"
arrayBook.forEach { book in
print(book.title)
}
Arrays are copied by value not reference, so when you are using filter you are creating a new array (different than the initial), but when you modify the new one, the initial one gets modified too because both are pointing to the same class (classed are passed by reference), so after the filter your array will have changed and the new one gets deallocated. So in this case it will print "default", "default" and then "default, "modified".
What happens if you change class for struct, the value will be passed by value not reference so you will have 2 arrays in memory with different values, so if you go through arrayBooks again it will print before the filter "default","default", and then "default", "default" again. Because when you are using the filter you are creating and modifying a new array that will get deallocated if you do not store it).
The solution is using map, creating a new array with all the values but with the modified items or fields that we want and then replace our array with the new one. This will print "default", "default" before the map, and then "default", "modified"
This will work with structs, classes and everything that you want :).
struct Book {
let id: Int
var title = "default"
init (id: Int) {
self.id = id
}
}
var arrayBook = [Book]()
arrayBook.append(Book(id: 0))
arrayBook.append(Book(id:1))
arrayBook.forEach { book in
print(book.title)
}
arrayBook = arrayBook.map{
var mutableBook = $0
if $0.id == 1 {
mutableBook.title = "modified"
}
return mutableBook
}
arrayBook.forEach { book in
print(book.title)
}
array = array.map { $0.eventID == id ? newValue : $0 }
If you conform your class to Equatable then this would work:
extension Array where Element: Equatable {
#discardableResult
public mutating func replace(_ element: Element, with new: Element) -> Bool {
if let f = self.firstIndex(where: { $0 == element}) {
self[f] = new
return true
}
return false
}
}
Use like this:
array.replace(prev, with: new)

Running! from VCVCVC

I have been trying for the last 18 months taking all my code out of the ViewController to make it more like MVC should. ( Hence the title )
I've made a small step on my own by being able to do the following within one Class object, but now I want to break it down further
A very generic example: one file contains the data
struct Data {
var x = 0
var y = 0
}
and one file for the operation
class Adder {
var myObject = MyClass()
var z = 1
func addThem() {
z = myObject.x + myObject.y
}
}
now for the salient parts of the ViewController:
var data = Data()
var adder = Adder()
#IBAction func buttonPressed(sender: UIButton) {
// user input via textfield
data.x = Int(numeralOne.text!)!
data.y = Int(numeralTwo.text!)!
adder.addThem() // *
answerLabel.text = String(adder.z)
}
Ultimately I'd like to omit the line commented with the asterisk. I thought OO's encapsulation of (data) away from (adder) allows for adder.z to just automatically update in the background without involving the ViewController. That way a subtractor class (say) can operate on the same two struct properties.
My question? How can correctly referencing them from the VC.
PS. if i include return statements in the function it makes no difference.
It's not clear from your code exactly what the relationship is between the Data struct and MyClass but if a MyClass object has the values of x & y that you need then the following will work
class MyClass {
var x = 1
var y = 2
}
class Adder {
var myObject = MyClass()
var z: Int {
return myObject.x + myObject.y
}
}
let adder = Adder()
print(adder.z)

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 = ""
}
}

Combining queries in Realm?

I have these two objects in my model:
Message:
class Message: Object {
//Precise UNIX time the message was sent
dynamic var sentTime: NSTimeInterval = NSDate().timeIntervalSince1970
let images = List<Image>()
}
Image:
class Image: Object {
dynamic var mediaURL: String = ""
var messageContainingImage: Message {
return linkingObjects(Message.self, forProperty: "images")[0]
}
}
I want to form a query which returns messages and images, messages sorted by sentTime and images sorted by their messageContainingImage's sent time. They'd be sorted together.
The recommended code for a query is this:
let messages = Realm().objects(Message).sorted("sentTime", ascending: true)
This returns a Result<Message> object. A Result doesn't have a way to be joined to another Result. There are other issues in my way too, such as, if I could combine them, how would I then perform a sort.
Additional thoughts:
I could also add a property to Image called sentTime, then once they're combined I'd be able to call that property on both of them.
I could make them both subclass from a type which has sentTime. The problem is, doing Realm().objects(Message) would only returns things which are messages, and not subclasses of Message.
How would I be able to do this?
My end goal is to display these message and image results in a tableview, messages separately from their attached image.
I think, inheritance is not the right solution here, this introduces more drawbacks by complicating your object schema, than it's worth for your use case.
Let's go back to what you wrote is your end goal: I guess you want to display messages and images together in one table view as separated rows, where the images follow their message. Do I understand that correctly?
You don't need to sort both, sorting the messages and accessing them and their images in a suitable way will ensure that everything is sorted correctly. The main challenge is more how to enumerate / random-access this two-dimensional data structure as an one-dimensional sequence.
Depending on the amount of data, you query, you have to decide, whether you can go a simple approach by keeping them all in memory at once, or introducing a view object on top of Results, which takes care of accessing all objects in order.
The first solution could just look like this:
let messages = Realm().objects(Message).sorted("sentTime", ascending: true)
array = reduce(messages, [Object]()) { (var result, message) in
result.append(message)
result += map(message.images) { $0 }
return result
}
While the latter solution is more complex, but could look like this:
// Let you iterate a list of nodes with their related objects as:
// [a<list: [a1, a2]>, b<list: [b1, b2, b3]>]
// in pre-order like:
// [a, a1, a2, b, b1, b2, b3]
// where listAccessor returns the related objects of a node, e.g.
// listAccessor(a) = [a1, a2]
//
// Usage:
// class Message: Object {
// dynamic var sentTime = NSDate()
// let images = List<Image>()
// }
//
// class Image: Object {
// …
// }
//
// FlattenedResultsView(Realm().objects(Message).sorted("sentTime"), listAccessor: { $0.images })
//
class FlattenedResultsView<T: Object, E: Object> : CollectionType {
typealias Index = Int
typealias Element = Object
let array: Results<T>
let listAccessor: (T) -> (List<E>)
var indexTransformVectors: [(Int, Int?)]
var notificationToken: NotificationToken? = nil
init(_ array: Results<T>, listAccessor: T -> List<E>) {
self.array = array
self.listAccessor = listAccessor
self.indexTransformVectors = FlattenedResultsView.computeTransformVectors(array, listAccessor)
self.notificationToken = Realm().addNotificationBlock { note, realm in
self.recomputeTransformVectors()
}
}
func recomputeTransformVectors() {
self.indexTransformVectors = FlattenedResultsView.computeTransformVectors(array, listAccessor)
}
static func computeTransformVectors(array: Results<T>, _ listAccessor: T -> List<E>) -> [(Int, Int?)] {
let initial = (endIndex: 0, array: [(Int, Int?)]())
return reduce(array, initial) { (result, element) in
var array = result.array
let list = listAccessor(element)
let vector: (Int, Int?) = (result.endIndex, nil)
array.append(vector)
for i in 0..<list.count {
let vector = (result.endIndex, Optional(i))
array.append(vector)
}
return (endIndex: result.endIndex + 1, array: array)
}.array
}
var startIndex: Index {
return indexTransformVectors.startIndex
}
var endIndex: Index {
return indexTransformVectors.endIndex
}
var count: Int {
return indexTransformVectors.count
}
subscript (position: Index) -> Object {
let vector = indexTransformVectors[position]
switch vector {
case (let i, .None):
return array[i]
case (let i, .Some(let j)):
return listAccessor(array[i])[j]
}
}
func generate() -> GeneratorOf<Object> {
var arrayGenerator = self.array.generate()
var lastObject: T? = arrayGenerator.next()
var listGenerator: GeneratorOf<E>? = nil
return GeneratorOf<Object> {
if listGenerator != nil {
let current = listGenerator!.next()
if current != nil {
return current
} else {
// Clear the listGenerator to jump back on next() to the first branch
listGenerator = nil
}
}
if let currentObject = lastObject {
// Get the list of the currentObject and advance the lastObject already, next
// time we're here the listGenerator went out of next elements and we check
// first whether there is anything on first level and start over again.
listGenerator = self.listAccessor(currentObject).generate()
lastObject = arrayGenerator.next()
return currentObject
} else {
return nil
}
}
}
}

Resources