Simple console display test - preview not working as expected

Just learning Swift and SwiftUI, having fun in Xcode. I have the following custom view defined, but when I try to test it, the information doesn't get updated as I expect. When I use the button defined INSIDE the custom view, the update works as expected. When I use the button defined in the Preview body, it doesn't. The "lines" variable of the custom view appears not to be updated in that case.

I know I'm missing something fundamental here about either view state binding or the preview environment, but I'm stumped. Any ideas?

import SwiftUI

struct ConsoleView: View {
    var maxLines : Int = 26
    private enum someIDs { case  textID}
    @State private var numLines : Int = 0
    @State var lines = "a\nb\nc\n"
    
    var body: some View {
        VStack(alignment: .leading, spacing:0 ) {
            ScrollViewReader { proxy in
                ScrollView {
                    Button("Scroll to Bottom") {
                        withAnimation {
                            proxy.scrollTo(someIDs.textID, anchor: .bottom)
                        }
                    }
                    
                    Text(lines)
                        .id(someIDs.textID)
                        .multilineTextAlignment(.leading)
                        .frame(maxWidth: .infinity, alignment: .bottomLeading)
                        .padding()
                        .onChange ( of: lines) {
                            withAnimation {
                                proxy.scrollTo( someIDs.textID, anchor: .bottom)
                            }
                        }

                }
                
                Button("Add more") {
                    writeln("More")
                    //writeln(response)
                }
                 
            }
        }

    }
    private func clipIfNeeded () {
        if (numLines>=maxLines) {
            if let i = lines.firstIndex(of: "\n") {
                lines = String(lines.suffix( from: lines.index(after:i)))
            }
        }
    }
    func writeln ( _ newText : String) {
        print("adding '\(newText)' to lines")
       //clipIfNeeded()
        write( newText )
        write("\n")
        numLines += 1
        print(lines)
   }
    func write ( _ newText : String) {
        lines += newText
    }
    
}

#Preview {
    VStack() {
        var myConsole = ConsoleView(lines: "x\ny\nz\n")
        myConsole
        
        Button("Add stuff") {
            myConsole.writeln("Stuff")
        }
         
    }
}
Answered by DTS Engineer in 826443022

myConsole.writeln("Stuff")

  • This calls a method outside the scope of your SwiftUI view which from your implementation mutates the state of your view.
  • First You don't want to mutate SwiftUI state outside the context of the view except through a binding or an observable object.

@State var lines = "a\nb\nc\n"

  • Declare state as private to prevent setting it in a memberwise initializer, which can conflict with the storage management that SwiftUI provides:

I recommend you check out some resources that dives into previews and state management in SwiftUI :

That's because I think you're using the preview in the wrong way.

The preview is there to show you what you've developed, i.e. your ConsoleView(), but you're adding an extra button into there that doesn't exist in the code you're testing.

Just use the preview like this:

#Preview {
	ConsoleView(lines: "x\ny\nz\n")
}

That previews the ConsoleView with the initial value (x, y, z).

You should use the preview to preview code you've developed. If you add extra things like that button, you're not really testing the code you've developed.

Accepted Answer

myConsole.writeln("Stuff")

  • This calls a method outside the scope of your SwiftUI view which from your implementation mutates the state of your view.
  • First You don't want to mutate SwiftUI state outside the context of the view except through a binding or an observable object.

@State var lines = "a\nb\nc\n"

  • Declare state as private to prevent setting it in a memberwise initializer, which can conflict with the storage management that SwiftUI provides:

I recommend you check out some resources that dives into previews and state management in SwiftUI :

Simple console display test - preview not working as expected
 
 
Q