Search code examples
arraysswiftcocoa-touchuipickerview

variable of object type as an Array misbehaving


environment XCode 6.4 Swift 2.something

I have two simple Custom Classes

class Stock {
    var ageArray = [3,6,9,12,15,18,24,30,36,42,48,54,60,66,72]  // in months
    var beastArray = ["Angus","Braford","Charolais","Droughtmaster","Euro","Hereford"]
    var breedArray = ["Calves","Vealers","Weaners","Heifers","Steers","Cows","Bulls"]
    var priceArray = [600.00,650.00,700.00,750.00,800.00,850.00,900.00,950.00,1000.00,]
}

and

class Sale {
    var age  : Int = 0
    var beast : String = " "
    var breed : String = " "
    var price : Double = 0.00
}

Now to my view controller

I've instantiated a report from Sale Class. The ViewController holds an array of all those reports as I'm taught in nuremous places, declaring it first and instatiating it in viewDidLoad

class ViewController: UIViewController,  UIPickerViewDelegate, UIPickerViewDataSource  {

    @IBOutlet weak var picker: UIPickerView!

    var report = Sale()
    var sheets = Stock()

    var saleArray : [Sale] = [] // declaration

override func viewDidLoad() {
        super.viewDidLoad()

        saleArray = [Sale]()    // instantiating
    }

the other delegate functions of the picker work sweetly, no need to show them all here, but this is where the two class Objects meet to pass values

func pickerView(picker: UIPickerView, didSelectRow row: Int, inComponent component: Int) {

    switch(component) {
    case 0:
        report.breed = sheets.breedArray[row]
        break

    case 1:
        report.beast = sheets.beastArray[row]
        break

    case 2:
        report.age = sheets.ageArray[row]
        break

    default:
        report.price = sheets.priceArray[row]
        break
    }
}

and an onscreeen button loads them into an array

@IBAction func AcceptValues(sender:UIButton) {

    saleArray.append(report)
}

that's all there is to it.

This is the result as seen in the debugger with a breakpoint set as below after 3 times around with [0] supposed to have Angus Calves and [1] having Droughtmaster Heifers

midnight colour scheme

so the question is why would the saleArray be filling up with n instances of whatever was last entered? I know i'm not looking at the last entry n times. But curiously the address for report is all the same at 7A19C000.

I thought it might be the compiler can't infer the array type after the instatiaton in viewDidLoad. Examining various tutorials and the code supplied with them treats the matter more or less as I have above. Which is the reason for the title, I'm sure Swift can hold an array of objects other than the usuals.

I've even gone to the expense at purchasing not one, but two of Matt Neuberg's books just to check on how to set up an Object Array, but like all the material available they just discuss arrays of Int and Double and String and no more. Sometimes the most infuriating bugs are the most staring-at-you-in-your-face, and I've been chasing this for close on a week.

There's little else to it the code.


EDIT: thanks to the two contributors below, the comments show what i did to make it work. But I have a feeling its not elegant: I also made Stock a struct.

class ViewController: UIViewController,  UIPickerViewDelegate, UIPickerViewDataSource  {

@IBOutlet weak var picker: UIPickerView!
@IBOutlet weak var textPanel: UITextView!

var sheets = Stock()
var saleArray : [Sale] = []

var localBreed = " "             // introduced some handover values
var localBeast = " "             // because report is out of scope
var localAge = 0                 // in the picker functions
var localPrice = 0.00

. . .

@IBAction func AcceptValues(sender:UIButton) { 

    var report = Sale()           // new reports instantiated here

    report.breed = localBreed     // and locals handover values to
    report.beast = localBeast     // report instance before adding
    report.age = localAge         // to array
    report.price = localPrice

    saleArray.append(report)
}

. . .

func pickerView(picker: UIPickerView, didSelectRow row: Int, inComponent component: Int) {

    switch(component) {
    case 0:
        localBreed = sheets.breedArray[row]
        break      
    case 1:
        localBeast = sheets.beastArray[row]
        break
    case 2:
        localAge = sheets.ageArray[row]
        break
    default:
        localPrice = sheets.priceArray[row]
        break
    }
}

Solution

  • I believe that it is because report is a single instance of Sale. Therefore each time you select a new combination - you are not creating a new Sale item, rather you are changing the values associated with that single instance of Sale called report. When you press the button it adds another reference to that single report instance.

    These class objects are passed by reference - i.e. when you add report to the saleArray it is not creating a copy of the values - rather you are passing a reference to the actual instance. Each time you press the button you are adding a reference to the same instance of Sale. And the values associated with that instance are then getting changed when you use the picker. It's the same instance over and over.

    You would need to change your code so that you create a new instance of Sale for each selection. As a quick hack, to test this, you could add code to your button which creates a new Sale instance based on the selected values - and then adds that to saleArray.

    ETA - one other thing I noticed: You are instantiating saleArray twice - both at //declaration and in viewDidLoad(). Although that is not causing the issue you have described, it could cause an issue under potential circumstances.