2

Simple sample code with toggle button (slightly modified from hackingwithswift: This code(hackingwithswift original and my version) IS redrawing every list cell whenever any toggle happens. I modified code to better debug view drawing.

import SwiftUI

struct User: Identifiable {
    let id = UUID()
    var name: String
    var isContacted = false
}

struct ProfileView: View {
    @State private var users = [
        User(name: "Taylor"),
        User(name: "Justin"),
        User(name: "Adele")
    ]

    var body: some View {
        let _ = Self._printChanges()
        List($users) { $user in
            ProfileCell(user: $user)
        }
    }
}

struct ProfileCell: View{
    @Binding var user: User
    var body: some View{
        let _ = Self._printChanges()
        Text(user.name)
        Spacer()
        Toggle("User has been contacted", isOn: $user.isContacted)
            .labelsHidden()
    }
}

Running app and toggling will print following in console for every toggle:

ProfileView: _users changed.
ProfileCell: @self, _user changed.
ProfileCell: @self, _user changed.
ProfileCell: @self, _user changed.

Hackingwithswift tutorial states "Using a binding in this way is the most efficient way of modifying the list, because it won’t cause the entire view to reload when only a single item changes.", however that does not seem to be true. Is it possible to redraw only item that was changed?

Oridadedles
  • 831
  • 1
  • 6
  • 8
  • https://developer.apple.com/wwdc21/10022 – lorem ipsum Aug 08 '22 at 12:40
  • are you referring to "stable identifiers"? this example use UUID that is not stable, i already tried with stable hardcoded id, no change @loremipsum – Oridadedles Aug 08 '22 at 12:52
  • Redrawing isn't the correct term here. SwiftUI diffs the old and new View structs and if there is no difference nothing is actually drawn. – malhal Aug 11 '22 at 20:58

1 Answers1

1

Theoretically it should be working, but it seems they changed something since first introduction, because now on state change they recreate(!) bindings (all of them), so automatic view changes handler interpret that as view update (binding is a property after all).

A possible workaround for this is to help rendering engine and check view equitability manually.

Tested with Xcode 13.4 / iOS 15.5

demo

Main parts:

// 1
List($users) { $user in
    EquatableView(content: ProfileCell(user: $user)) // << here !!
}

// 2
struct ProfileCell: View, Equatable {
  static func == (lhs: ProfileCell, rhs: ProfileCell) -> Bool {
    lhs.user == rhs.user
  }

  // ...

// 3
struct User: Identifiable, Equatable {

Test module is here

Asperi
  • 228,894
  • 20
  • 464
  • 690