How to Reset a Subview in Swiftui

How to reset a subview in SwiftUI?

SwiftUI constructs a view tree from View objects in body of their parents.

So, what SwiftUI got was the initial copy (remember, it's a value-type struct) of myKeyboard, not the copy you are changing.

Under normal usage, you don't keep instances of various View stored as variables (I mean, you can, but you'd need to understand in depth what's going on).

What you probably want is to change the data that drives the child view. Where does (should) this data live? It depends on what you want to do.


In most cases the parent "owns" the state, i.e. has the source of truth of some data that the child relies on. Then it's trivial to change the state and pass the state to the child via its init:

struct ChildView: View {
let number: Int

var body: some View {
Text("\(number)")
}
}

struct ParentView: View {
@State var random: Int = Int.random(1...100)

var body: some View {
VStack() {
ChildView(number: random)
Button("randomize") {
self.random = Int.random(1...100)
}
}
}
}

But, say, the parent doesn't want to do the randomization - i.e. the child should deal with it.

The proper approach is to create a view model for the child, which the parent (or the parent's own view model) could own and pass via init, and then the view model would deal with nuances of randomization.

class ChildVM: ObservableObject {
@Published var random = Int.random(1...100)

func change() {
random = Int.random(1...100)
}
}

The parent creates an instance of ChildVM and passes it to the child:

struct ParentVuew: View {
let childVM = ChildVM()

var body: some View {
VStack() {
ChildView(vm: childVm)
Button("randomize") {
self.childVM.change()
}
}
}
}

And the child view is simply driven by the view model:

struct ChildView: View {
@ObservedObject let vm: ChildVM

var body: some View {
Text("\(vm.random)")
}
}

Obviously, this is a simplified example that could have been achieved in any number of ways.

And there are different ways for the parent to "message" the child.

But the general takeaway should be that Views should be thought of as declarative structures - not living instances - and the data is what drives the changes in those views. You need to decide who is best to own the source of truth.

Child subviews not resetting when a new parent is created in SwiftUI

I'll repeat what I said in an answer to your previous question - under most normal use cases you shouldn't instantiate views as variables, so if you find yourself doing that, you might be on the wrong track.


Whenever there's any state change, SwiftUI recomputes the body and reconstructs the view tree, and matches the child view states to the new tree.

When it detects that something has changed, it realizes that the new child view is truly new, so it resets its state, fires .onAppear and so forth. But when there's no change that it can detect, then it just keeps the same state for all the descendent views.

That's what you're observing.

Specifically, in your situation nothing structurally has changed - i.e. it's still:

GameView
--> KeyboardView
--> ButtonView
ButtonView
ButtonView
...

so, it keeps the state of ButtonViews as is.

You can signal to SwiftUI that the view has actually changed and that it should be updated by using an .id modifier (documentation isn't great, but you can find more info in blogs), where you need to supply any Hashable variable to it that's different than the current one, in order to reset it.

I'll use a new Bool variable as an example:

struct GameView {
@State var id: Bool = false // initial value doesn't matter

var body: some View {
VStack() {
KeyboardView()
.id(id) // use the id here
Button("new game") {
self.id.toggle() // changes the id
}
}
}
}

Every time the id changes, SwiftUI resets the state, so all the child views', like ButtonViews', states are reset.

Reset main content view - Swift UI

The possible approach is to use global app state

class AppState: ObservableObject {
static let shared = AppState()

@Published var gameID = UUID()
}

and have root content view be dependent on that gameID

@main
struct SomeApp: App {
@StateObject var appState = AppState.shared // << here

var body: some Scene {
WindowGroup {
ContentView().id(appState.gameID) // << here
}
}
}

and now to reset everything to initial state from any place we just set new gameID:

Button("New Game"){
AppState.shared.gameID = UUID()
}

Reset View on Button Click SwiftUI

you could try this approach using a ObservableObject model to keep track of your Cell state. For example:

class CellViewModel: ObservableObject {
@Published var cells = [Cell]()

func resetAll() {
for i in cells.indices {
cells[i].isDisabled = false
}
}

}

struct CellView: View {
@Binding var cell: Cell

var body: some View {
Button {
cell.isDisabled = true
} label: {
Text("\(cell.value)").frame(width: 100, height: 100)
}.disabled(cell.isDisabled)
.border(Color.black)
}
}

struct ContentView: View {
@StateObject var cellModel = CellViewModel()

var body: some View {
VStack {
List {
ForEach($cellModel.cells) { $cell in
CellView(cell: $cell)
}
}
Button("Reset all") {
cellModel.resetAll()
}
}
.onAppear {
cellModel.cells.append(Cell(value: 1))
cellModel.cells.append(Cell(value: 2))
}
}
}

struct Cell: Identifiable, Hashable {
let id = UUID()
let value: Int
var isDisabled = false
}


Related Topics



Leave a reply



Submit