Arrays and Initialisation: Bug or "feature"? (and some interesting array trickery!)

So is this a bug or feature or I am not understanding the rules!

I create a variable: @State public var thisSite: bores

The error I get when I create a variable is: Return from initializer without initializing all stored properties

Note: the init() for the struct initialises EVERY MEMBER (see code below)

so to fix this I call it explicitly in the View init(): thisSite = bores.init()

Why do I need to call the init() this way?

Why not simply: thisSite() or thisSite.init()


basic code which is minimal to reduce reading (see if you can spot some array trickery :-)!!)

// a single row of data from the soil table
struct boreRow  {
    var type: String
    init()
    {
        self.type = "FILL"
    }
}


// each bore
struct boreData  {
    var boreA: [boreRow]
    init ()
    {
        boreA = Array(repeating: boreRow(), count: 30)
    }
}


// the array of bores
struct  bores {
    var boresA: [boreData]
    init ()
    {
        boresA = Array(repeating: boreData(), count: 2)
    }
}


Sorry, not an answer for your question, but FYI, it’s good form to capitalise the first letter when defining types. E.g.

// a single row of data from the soil table
struct BoreRow  {
    var type: String
    init()
    {
        self.type = "FILL"
    }
}

Your code appears to compile for me in Swift Playground on iPad.

Sometimes Xcode can show lingering errors after you have actually fixed the error.

import SwiftUI

// a single row of data from the soil table
 struct BoreRow  {
  var type: String
  init()
  {
      self.type = "FILL"
  }
}


// each bore
struct BoreData  {
  var boreA: [BoreRow]
  init ()
  {
      boreA = Array(repeating: BoreRow(), count: 30)
  }
}


// the array of bores
struct Bores {
  var boresA: [BoreData]
  init ()
  {
      boresA = Array(repeating: BoreData(), count: 2)
  }
}

struct ContentView: View {
  
  let bores = Bores()
  var body: some View {
      VStack {
          Image(systemName: "globe")
              .imageScale(.large)
              .foregroundColor(.accentColor)
          Text("Hello, world!")
      }
  }
}

Yes agree, using old C++ syntax :frowning:

But let is a constant and cant be changed so has to be "var"

but if you declare it as: var thisSite = bores() generates a " required to conform to view ..." error when you try to access the components ...

The way I have detailed is the ONLY way I have found to get it to work (and it does work) but I have no idea how it is working or why ...


struct ContentView: View {
    @State public var bores = Bores()
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text("Hello, world!")
            Text("Bores count: \(bores.boresA.count)")
            Text("BoresA Rows count: \(bores.boresA.first?.boreA.count ?? 0) ")
        }
    }
}

Works for me:

Hello, world!
Bores count: 2
BoresA Rows count: 30

Sorry, I'm probably missing something...

Actually, this question is more about SwiftUI than Swift the language. :slight_smile:

Now you have the opposite problem. :upside_down_face: You are asking about arrays, but your problem has nothing to do with them specifically. Array is a value type in Swift, and your problem revolves around value type mutation.

struct ContentView: View {
  var mutable = 0
  var body: some View {
    mutable = 1 // Cannot assign to property: 'self' is immutable
    EmptyView()
  }
}

Take SwiftUI away for a moment. Your problem reduces to this:

struct ValueType {
  var mutable = 0
  var property: Void {
    mutable = 1 // Cannot assign to property: 'self' is immutable
    return // The return type is irrelevant to the problem.
  }
}

Property get accessors cannot mutate self. By default, get is nonmutating. All of these are the same. The first two are shorthand for the full form, nonmutating get.

struct ValueType {
  var property: Void { return }
}
struct ValueType {
  var property: Void {
    get { return }
  }
}
struct ValueType {
  var property: Void {
    nonmutating get { return }
  }
}

To compile, the get must be mutating, instead.

struct ValueType {
  var mutable = 0
  var property: Void {
    mutating get {
      mutable = 1
      return
    }
  }
}

But SwiftUI's View protocol requires that body be nonmutating.

  1. If you're going to use SwiftUI, you instead use property wrappers like @State and @Binding to mutate the variables that are associated with a View.
  2. body is not a place to perform imperative code. Imperative code is not compatible with ViewBuilder, which body uses. There are many correct options available, but which one is appropriate is dependent on use case.
struct ContentView: View {
  @State var int = 0
  var body: some View {
    int = 1  // 'buildExpression' is unavailable: this expression does not conform to 'View'
    EmptyView()
  }
}
2 Likes