UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Code works except when trying to display element of array - get an index out of bounds error

Forums > SwiftUI

import SwiftUI

let shortNames = ["None", "HFC227EA", "TCE1112", "F134A", "HCFC142B", "TCE111", "TFACTONE"]

private var yValues: [Double] = []

struct ContentView: View {

    @State private var yScale: Double = 0

    @State private var selectedFile = "None"
    @State private var fullFileName = "None"

    @State private var specs = [Spec]()
    @State private var npts: Int = 0

    var body: some View {

        VStack(alignment: .leading) {
            Picker("Select short name:", selection: $selectedFile) {
                ForEach(shortNames, id: \.self) { fileName in
                    Text(String("\(fileName)"))
                }
            }
            .padding()

            List () {
                Text(fullFileName)
                Text(String("y-Scale = \(yScale)"))
                Text(String("Number of points in yDdata = \(npts)"))
                Text(String("Y-value = \(yValues[5])"))
                }
            }

        .onChange(of: selectedFile) { _ in
            fullFileName = selectedFile + ".JSON"
            specs = load(fullFileName)

            npts = specs[0].yData.count
            yScale = specs[0].yScale    /* Scaling factor for decompressing absorbance yData */

            yValues = [Double] (repeating: 0, count: npts)

            yValues = calcAbsorb( npts: npts, yScale: yScale, yData: specs[0].yData )            
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

// Function to fill "yData" array with scaled absorbance values
// First make a new, Double array called "yValues"
func calcAbsorb(npts: Int, yScale: Double, yData: [Int] ) -> [Double] {
    for ii in 0...(npts-1) {
        yValues[ii] = Double(yData[ii]) / yScale
    }
    return yValues
}

Suspect that the array "yValues" not being passed to View closure? What am I doing wrong?

1      

Try this

func calcAbsorb(npts: Int, yScale: Double, yData: [Int] ) -> [Double] {
    var yValues = [Double]()
    for ii in 0...(npts-1) {
        // check that number of elements in yData has more then (npts-1)
        guard ii < yData.count else { continue }

        yValues.append(Double(yData[ii]) / yScale)
    }
    return yValues
}

1      

Thanks @NigelGee. Definitely should have included the Guard statement. But that is not the origin of the error. When I set a breakpoint (debugging) after the last line of code in the .onChange closure, I can see that yValues has the correct number of elements with the correct values. For whatever reason, yValues is not updating in Some View. I'm struggling with Declarative coding as my background has been Imperative code.

1      

You initialize yValues with an empty array:

private var yValues: [Double] = []

But in the body of your ContentView, you access the 5th index of the array:

Text(String("Y-value = \(yValues[5])"))

yValues is empty at that point, so of course your code crashes.

You need to account for an empty array in your code or make sure the array is never empty.

1      

Yes, yValues is empty when I define the array. But in the .onChange closure, the array yValues is dimensioned and filled with Doubles. Do I need to first initialize yValues with enough elements (i.e., some number greater than npts) and fill with zeros? Or is it inappropriate to display yValues in Some View?

1      

But in the .onChange closure, the array yValues is dimensioned and filled with Doubles.

But onChange isn't fired until the value of selectedFile changes, which hasn't happened yet when the View first loads.

Do I need to first initialize yValues with enough elements (i.e., some number greater than npts) and fill with zeros?

You need to account for an empty array in your code or make sure the array is never empty.

Or is it inappropriate to display yValues in Some View?

I'm not sure what exactly its purpose is there, so I can't really answer that.

(BTW, The name of the View you are displaying this stuff in is actually ContentView. some View is the data type returned by the body computed property of ContentView, not the name of a View.)

1      

OK. Makes perfect sense now. Thank you.

1      

You could put this around Text(String("Y-value = \(yValues[5])"))

if yValues.isEmpty == false {
  Text(String("Y-value = \(yValues[5])"))
}

Now will not be called until the .onChange is called. You might want to add && yValues.count >= 6 to the if statement just to make sure that element 5 is there!

1      

@NigelGee- As you suggested, wrapping

Text(String("Y-value = \(yValues[5])"))

in a conditional was part of the solution. I also had to re-arrange some of the code to update ContentView AFTER filling the yValues array. I do appreciate the assistance from both you and @roosterboy. As an aside and to give some perspective on my programming experience, I was programming in Fotran for many years. I guess that officially makes me old ;-)

1      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.