Peter Suwara
Peter Suwara

Reputation: 816

SwiftUI - Struct Binding doesn't update UI as expected

I have a SwiftUI question which doesn't look like it's answered or a duplicate of another issue, and SwiftUI is rather new.

The following code, with my understanding, show toggle the background of the Button control. The number of likes increments as expected, but the state changes don't seem to pass through to the Book struct.

Everything I have read is that in the preview, you need to use: .constant(x) within the view of the PreviewProvider.

However, it doesn't work and the Button with likes does not the foreground, nor the background color.

When debugging the app, the result of the book isLiked does change. But the foreground/background colors do not.

Here is the code for the view.

import SwiftUI

struct BookDetailView: View {

  @Binding var book: Book
    @State var likes: Int = 0
  var body: some View {
    VStack {

      Text(book.title)
      Image(book.imageName)
        .resizable()
        .aspectRatio(contentMode: .fit)
        .scaledToFit()

        Button(action: {
            self.book.isLiked.toggle()
            self.likes = self.likes + 1
        }) {
            Text("👍 Like \(likes)")
                .padding()
                .foregroundColor(self.book.isLiked ? .secondary : .primary)
                .background(self.book.isLiked ? Color.rayWenderlichGreen : Color.white)
                .cornerRadius(10)
        }
    }
  }
}

struct BookDetailView_Previews: PreviewProvider {

  static var previews: some View {
    BookDetailView(book: .constant(Book.demoBooks.randomElement()!))
  }
}

Here is the actual book model

import SwiftUI

struct Book: Identifiable {
  var id = UUID()
  var title: String
  var imageName: String
  var isLiked = false
}

extension Book: Equatable {
  static func == (lhs: Book, rhs: Book) -> Bool {
    return lhs.id == rhs.id
  }
}

I have invested some time in trying to understand why this is happening, and it seems like it could just be a bug with SwiftUI.

Any thoughts or suggestions?

Upvotes: 3

Views: 1755

Answers (2)

Asperi
Asperi

Reputation: 257493

I cannot test because there is no Book type provided, but if it is a struct, the following should work (note: .constant binding is constant, it does not modify wrapped value, you need alive one)

struct BookDetailView_Previews: PreviewProvider {
  struct TestView: View {
     @State private var book = Book.demoBooks.randomElement()!
     var body: some View {
       BookDetailView(book: $book)
     }
  }
  static var previews: some View {
    TestView()
  }
}

Update: now with fix - the reason is that made equatable book does not take into account changed isLiked state, so view does not detect that anything changed and does not update.

The solution is either remove that extension or make it take into account all fields that should indicate modified value, as below

extension Book: Equatable {
  static func == (lhs: Book, rhs: Book) -> Bool {
    lhs.id == rhs.id && lhs.isLiked == rhs.isLiked
  }
}

Tested & works with Xcode 11.4 / iOS 13.4

Upvotes: 3

Pastre
Pastre

Reputation: 743

Your code seems OK and your understanding, usage and implementation of the State concept appears to be good.

A weird thing is that this happens to me as well. Sometimes, State var just won't reload the view, and I believe it's because SwiftUI is unstable. I have a few ways to "fix" it.

  1. I create a @State private var toggleMe: Bool! = false and call toggleMe.toggle() to toggle it whenever I need a state change. This sometimes updates the UI
  2. I stop using States and create a custom OberveableObject like in this tutorial, because then I can manually signal when the state will change by calling objectWillChange.send() and it seems to work better then changing a State var, for some reason

None of these suggestions are good practice for your case. They are just a way of dealing with some stability issues SwiftUI has shown because it is new

Upvotes: 3

Related Questions