• Hello MLAers! We've re-enabled auto-approval for accounts. If you are still waiting on account approval, please check this thread for more information.

Swift, SwiftUI Newbie Question

Snial

68000
Can anyone help me here? I wanted to play around with a bit of Swift and SwiftUI (because that's what Apple are promoting). I've read quite a bit in the documentation and watched a few videos, but frankly there's something I'm not getting about declarative UIs.

Here's some code that illustrates the problem:

TestApp.swift:
Swift:
import SwiftUI

@main
struct TestAppApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

ContentView.swift:

Swift:
import SwiftUI

struct TitleButton: View {
    @State var iTitle:String
    var body: some View {
        Button(action: theAction) {
            Text(iTitle)
        }.buttonStyle(.borderedProminent)
    }
    init(aTitle:String) {
        iTitle=aTitle
    }
    func theAction() { iTitle="Hello"+iTitle }
    func goodbye() {  iTitle="Goodbye"; print("Goodbye") }
}

struct ContentView: View {
    @State private var iTitles: [String] = ["Woo!"]
    @State private var iButtons: [TitleButton]=[] // = [TitleButton(aTitle:self.iTitles[0])]
    var body: some View {
        VStack {
            Button("Append", action: {
                iTitles.append("Woo!")
                iButtons.append(TitleButton(aTitle:iTitles[iTitles.count-1]))
            })
            Button("Goodbye", action: {
                if iButtons.count > 0 { iButtons[0].goodbye() }
            })
            ForEach(0..<iButtons.count, id:\.self) {id in
                iButtons[id]
            }
        }
    }
}

The App creates a dynamic array of buttons. The iTitle of which is always "Woo", so the first button initially appears as: "Woo". The array can be extended by tapping on the first button, called "Append". Pressing a button prepends: "Hello" to the title, so multiple presses change the title to: "HelloWoo", "HelloHelloWoo" etc. That's fine, that works.

What doesn't work is if you tap the button "Goodbye". Although it's meant to invoke the "Goodbye" method of iButtons[0], in fact it doesn't, because iButtons[0] in that part of the code isn't iButtons[0] in the ForEach. It's a different object. And this happens even if you don't add any buttons by pressing "Append": iButtons[0] in the "Goodbye" section of the code always was a different iButtons[0] to the one in the ForEach, even though iButtons[] in the "Append" section is the same iButtons[] in the ForEach.

Now, I sort of get the concept of a declarative UI. It's more like a hardware description language than a procedural or object-oriented UI. The VStack and ForEach work like HTML user-interface elements rather than a sequence of steps, but also the preference is for them to be immutable, because somehow in the deluded minds of Gen Z programmers, creating zillions more objects every time you need to do something to them is safer and quicker.

Still, in the real world, Swift provides @state annotations that allow UI elements to contain mutable data: changes to their data trigger an update in the UI elements themselves.

It seems to me though that I can't get two methods to operate on a single, dynamically created, button and yet surely this kind of functionality is critical for pretty much every app? And yet the documentation I've seen doesn't seem to cover this. Yes, they cover dynamic allocation (with seemingly a single method) or static allocation with multiple methods. But not both.

1751643644630.png1751643723471.png

Sequence: Append, tap, tap, Append, tap (on the second one), Goodbye, tap (on the first one). Goodbye didn't modify iButtons[0] text.

So, there's 2 questions:

  1. What am I not getting?
  2. How would I fix it so that the Goodbye button actually does change the first button's text to "Goodbye", update it on the screen and then subsequent presses of the button would change it to: "HelloGoodbye", "HelloHelloGoodbye".
-cheers from Julz
 
Heh... I can't answer your question directly, as I've stayed away from Swift UI for the most part, but this really really REALLY reminds me of doing the exact same exercise in HyperCard in 1989.

I wonder if trying to sort it out there, and then using that insight to figure it out in Swift would work? That way, you're splitting the language constraints from the declarative UI bit.
 
Heh... I can't answer your question directly, as I've stayed away from Swift UI for the most part, but this really really REALLY reminds me of doing the exact same exercise in HyperCard in 1989.

I wonder if trying to sort it out there, and then using that insight to figure it out in Swift would work? That way, you're splitting the language constraints from the declarative UI bit.
It's amazing how one encounters cognitive hurdles (mostly abstractions) when learning new languages.
  • First computer (aged 12-13, 1980-1981): ZX80. Personal Computer World listed a ZX80 "Duck Shoot" reaction game, which contained some machine code to pause the screen until a key was pressed. On a ZX80 that was a major achievement, because it generated the display largely in software and could display and compute at the same time. Anyway, the machine code was listed as decimal, mnemonics and comments, but I didn't understand I was only supposed to type the decimals: this left me confused about how machine code could possibly work for 6 months to a year.
  • Second computer (aged 14, early 1982): ZX81. I still hadn't understood string arrays and at one point wanted to store a string result in one of a multiple of strings. I tried doing things like LET CHR$(38+N)$="HELLO" thinking that e.g. if N=0, it would evaluate to A$="HELLO" and N=26 it'd become Z$="HELLO" (because CHR$(38) on a ZX81 is 'A'). Of course, I should have used an array of strings.
  • University first year (18-21, 1986-1989). By then I knew a decent amount of Z80 assembler (enough to say, write a disassembler). So, I understood the concept of address and index registers (BC, DE, HL, BC', DE', HL' and IX, IY), but strangely enough spent a whole month of our Pascal course failing to understand what pointers were. I couldn't grasp how a piece of data could actively point somewhere else. Of course, they're nothing but addresses that need indirecting. However, ... some computers in the 1960s and 1970s, like the DG Nova really could support cells of data that would cause an indefinite number of indirections just by accessing them, if the MSB was 1.
  • Micro Control Systems (24-25, 1992-1993). I didn't really understand object-oriented software until after Uni and was in my first full-time embedded programming job. The book "Object Oriented Programming: An Evolutionary Approach" by Brad Cox. The key for me was how it explained the underlying data structures and how methods were invoked from messages. A great book!
I face something similar with declarative programming in SwiftUI.
 
The short answer is you aren't meant to observe Views as State. Views are a function of state.

Longer (AI generated because I'm on a phone) explanation:

Views are Ephemeral: SwiftUI views are lightweight structures that are frequently recreated by the framework as needed to reflect changes in the application's state. They are not designed to hold data that needs to persist across these re-creations.

State Management is SwiftUI's Job: The @State property wrapper is how you tell SwiftUI to manage the storage and lifetime of a variable, and to automatically re-render the view when that variable changes. When you declare a variable with @State, you're essentially handing control of that data to SwiftUI's state management system.

Views Describe UI, Not Hold Data: Views are intended to be a function of state. When the data represented by your state changes, SwiftUI observes this and re-renders the view to reflect the new state, but it does so by creating new view instances based on the updated data.

To accomplish what you're trying to do, you could just have your Append and Goodbye buttons manage an array of title strings as State in your ContentView and then the ForEach would render a TitleButton View for each string in that array. Since it's State, SwiftUI will automatically rerender your ContentView whenever you update the string array. TitleButton shouldn't need any State of its own.
 
Back
Top