@State Var Not Updated as Expected in Lazyvgrid

@State var not updated as expected in LazyVGrid

Sheet is picky about when it loads its content with isPresented.

A more reliable solution is to use sheet(item: ), which will work with your situation with just a small modification to selectedItem -- it'll have to conform to Identifiable. So, you can wrap it like this:

struct ImageSelection : Identifiable {
var name : String
var id: String {
return name
}
}

Then, selectedItem will become an optional, because it will determine whether the sheet is open. Here's a minimal example showing the optional and how you use it with sheet(item:):

struct ContentView : View {
@State var selectedItem : ImageSelection?

var body: some View {
Text("Test")
.sheet(item: $selectedItem) { item in
Text(item.name)
}
}
}

(Note that item is passed into the sheet's closure -- that's how you make sure that the correct data is used)

Update
Based on your comments:

selectedItem = ImageSelection(name: imageSet[index].name)
print(selectedItem?.name)

@State var not updating View as expected

In the UserSelectionIcon you have to use Bindings to access the currentSelectedIndex instead of States, as States won't update if you change a value in another view.

Therefore you have to safe the Bool 'selected' as Binding in your UserSelectionIcon view. Change this:

@State var selected: Bool = false

to this:

@Binding var selected: Bool

and than just refactor this call:

UserSelectionIcon(room: rooms[roomIndex], selected: roomIndex == currentSelectedIndex)

to this one:

UserSelectionIcon(room: rooms[roomIndex], selected: Binding(get: { roomIndex == currentSelectedIndex }, set: {_,_ in }))

Now you're wrapping the comparison of the two indices into a binding which will all the time represent the latest comparison of both indices. If you're passing just the comparison then it's like a one-time-value which won't refresh it's result if one of the indices is updated. But now the comparison is reevaluated every time you access the binding when selecting which color should be chosen for the stroke border.

Alternative Solution:

You should refactor this:

UserSelectionIcon(room: rooms[roomIndex], selected: roomIndex == currentSelectedIndex)

to this (to pass the State variable):

UserSelectionIcon(room: rooms[roomIndex], currentSelectedIndex: currentSelectedIndex)

and in your UserSelectionIcon you should to refactor this line:

@State var selected: Bool = false

to:

 @Binding var currentSelectedIndex: Bool

and every icon should save it's index itself to compare it later:

var roomIndex: Int

then you can compare these two indices when displaying the border.

.strokeBorder(currentSelectedIndex == roomIndex ? Color.blue.opacity(0.7) : Color.clear, lineWidth: 2)

Update LazyVGrid list

In order for your SwiftUI View to know to update, you should use an ObservableObject with a @Published property and store it as a @ObservedObject or @StateObject:

class BluetoothManager: NSObject, ObservableObject {
static let shared = BluetoothManager()
private override init() { }
@Published var stations: [BHStation] = []
}
struct ContentView : View {
@ObservedObject private var manager = BluetoothManager.shared

var body: some View {
let columns: [GridItem] = Array(repeating: .init(.flexible()), count: UIDevice.current.userInterfaceIdiom == .pad ? 2 : 1)
ScrollView {
LazyVGrid(columns: columns, alignment: .center, spacing: 10, pinnedViews: [], content: {
ForEach(manager.stations, id: \.peripheral.identifier) { item in //<-- Here
NavigationLink(destination: DetailView()) {
MainCell()
}
}
})
}
}
}

The @Published may interfere with your @objc requirement, but since you haven't given any information on how you use it in UIKit or why it's necessary, it's not possible to say directly what the fix is. You may need a different setter to use when interacting with it in UIKit.

Also, be aware that @Published won't update as expected unless the model type it is storing (BHStation) is a struct. If it's a class, you may need to call objectWillChange directly to get the @Published publisher to trigger correctly.

SwiftUI LazyVGrid transition animation not as expected

I can reproduce your issue, and I don't know why it happens.

But I found a possible solution. If you wrap the LazyVGrid in s ScrollView it works:

struct Item: Identifiable {
let id = UUID()
}

struct ContentView: View {

@State private var data: [Item] = []
let column = Array(repeating: GridItem(.flexible()), count: 3)

var body: some View {
VStack {
Button("Add") { handleButtonTap() }

ScrollView {
LazyVGrid(columns: column, alignment: .leading, spacing: 5) {
ForEach(data) { _ in
Color.red
.frame(height: 150)
.transition(.move(edge: .trailing))
}
}
Spacer()
}
}
}

func handleButtonTap() {
for index in 0..<9 {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(1 * index)) {
withAnimation(.easeInOut(duration: 1)) {
let newItem = Item()
self.data.append(newItem)
}
}
}
}

}

Sample Image

@State property value is not retain when called within the sheet(item:) method

I think you're getting caught by the fact that sheet(item:) only re-renders based on item -- not the encapsulating View's @State variables. As long as you explicitly pass isReseting through the item, it will work:


struct Item: Identifiable, Equatable {
var id = UUID()
var name:String
var active:Bool
}

struct ContentView: View {

var items:[Item] = [Item(name: "Oranges", active: true),
Item(name: "Apples", active: false),
Item(name: "Cookies", active: false) ]

struct SheetItem : Identifiable {
var item: Item
var isReseting: Bool

var id: UUID {
self.item.id
}
}

@State private var selectedItem: SheetItem?
@State private var isReseting: Bool?

var body: some View {
List{
ForEach(items){ item in
HStack{
Text(item.name)

Button(item.active ? "Reset": "Initiate"){
isReseting = true
selectedItem = SheetItem(item: item, isReseting: isReseting ?? false)
}
.padding()
.background(item.active ? Color.blue: Color.gray)
.foregroundColor(.white)
.cornerRadius(30)
.frame(width: 100, height: 65)
}
}
}
.sheet(item: $selectedItem) { item in
let _ = print("Value in sheet: \(item.isReseting)")

if item.isReseting == true {
Text("It's reseting!!!")
} else {
Text("It's NOT reseting")
}
}
}
}

A second way to accomplish this is explicitly pass a Binding (even though you don't need to mutate it in the sheet), since it will maintain its link to the @State variable:

struct Item: Identifiable{
var id = UUID()
var name:String
var active:Bool
}

struct ContentView: View {

var items:[Item] = [Item(name: "Oranges", active: true),
Item(name: "Apples", active: false),
Item(name: "Cookies", active: false) ]

@State private var selectedItem: Item?
@State private var isReseting: Bool?

var body: some View {
List{
ForEach(items){ item in
HStack{
Text(item.name)

Button(item.active ? "Reset": "Initiate"){
selectedItem = item
isReseting = true
let _ = print("Value after button tap: \(isReseting)")// output: Value after button tap: Optional(true)
}
.padding()
.background(item.active ? Color.blue: Color.gray)
.foregroundColor(.white)
.cornerRadius(30)
.frame(width: 100, height: 65)
}
}
}
.sheet(item: $selectedItem){ item in
// I'm expecting isReseting to be true here, but it's nil
SheetView(isReseting: $isReseting)
}
}
}

struct SheetView : View {
@Binding var isReseting : Bool?

var body: some View {
let _ = print("Value in sheet: \(isReseting)")// outputs: Value in sheet: nil

if isReseting == true {
Text("It's reseting!!!")
}else{
Text("It's NOT reseting")
}
}
}



Related Topics



Leave a reply



Submit