When SwiftUI View is Equatable - questions...

I need to optimize my SwiftUI code, and views conforming to Equatable seem to a good way forward. However, I have some questions, that could be also an interest of others.

  • View can conform to Equatable, and NOT encapsulated in EquatableView nor .equatable() view modifier, and modern SwiftUI implementations still can use the Equatable functionality, right?
  • When view contains @ObservedObject or @EnvironmentObject, then static func == (...) have to handle the changes of the observed objects, otherwise the view is not updated after the observed object published change. At least this is my experience. Correct?
  • Does view, that is Equatable, need to care about equality of its subviews? I hope no. I hope that view can evaluate equality only based on properties available to it, and SwiftUI takes responsibility for its subviews. I. e.: Even when some view returns true on ==, but something changes in its subview (detail hidden to the view), SwiftUI is able to recognize the change and takes appropriate actions. Correct?
  • When view returns true on equality check (==), but some environment value changes (not visible to the view), the view still reflects the change. Correct?
  • When the observed object conforms to Equatable, it is being used by SwiftUI. For what purpose? In most (if not all) cases, lhs and rhs are the same instance, so == returns true.

∙View can conform to Equatable...

True, .equatable() is no longer required, not sure what version that changed in.

.

∙When view contains @ObservedObject...

False, regardless of the outcome of ==, body is always called when any @Published property of the object changes or objectWillChange is published.

.

∙Does view, that is Equatable, need to care about equality of its subviews..

No, a subview will just follow the same algorithm. Best to only init a View with the data it actually needs and uses in its body to keep invalidation tight.

.

∙When view returns true on equality check...

True, if you returned true from == but had @Environment(\.calendar) var calendar and the calendar changed the body would still be called. This is the same behaviour as @ObservedObject in the 2nd question.

.

∙When the observed object conforms to Equatable...

In my testing the if an ObservedObject conformed to Equatable the == func was never called.

My test code:

import SwiftUI
import Combine

struct ViewTest {

    class MyObject: ObservableObject, Equatable {
        static func == (lhs: ViewTest.MyObject, rhs: ViewTest.MyObject) -> Bool {
            // not called
            let x = lhs.counter1 == rhs.counter1 && lhs.counter2 == rhs.counter2
            return x
        }
        
        @Published var counter1 = 0
        @Published var counter2 = 0
        
        func inc() {
            counter1 += 1
        }
        
    }
    
    struct ContentView: View {
        @State var counter = 0
        @State var counter2 = 0
        @StateObject var object = MyObject()
        
        var body: some View {
            Form {
                Button("Increment \(counter)") {
                    counter += 1
                }
                Button("Increment \(counter2)") {
                    counter2 += 1
                }
//                ContentView2(x: counter) { [c = counter] in
//                    print("\(c)") // body is called once when counter2 is changed the first time (once after every time if it has the onReceive
//                }

                ContentView2(x: counter2)
                    .environment(\.calendar, ((counter % 2 == 0) ? Calendar.current : Calendar(identifier: .chinese)))
                //{
                  //  print("\(self.counter)") // body called every time counter2 is changed
               // }
            //    .equatable()
                                  }
        }
    }
    
    
    
    struct ContentView2: View, Equatable {
      //  @Environment(\.managedObjectContext) var context
        //@State var counter = 0
        //@ObservedObject var object: MyObject
        @Environment(\.calendar) var calendar
        let x: Int
       // let y: () -> () // if the closure passed in does somethiung with self then body is always called.
        
        
        static func == (lhs: Self, rhs: Self) -> Bool {
            let result = lhs.x == rhs.x 
            //&& lhs.object.counter2 == rhs.object.counter2
            return result
        }
        
        var body: some View {
            let _ = Self._printChanges()
            HStack {
                Text("ff")
                Button("Button") {
                    //y()
                }
            }
//                .onReceive(Just(0)) { _ in // causes body to be called
//                    print("")
//                }
//            .task {
//                    counter = 0
//                }.onChange(of: x) { a in
//                    
//                }
//                .onAppear {
//                    counter = 0
//                }
        }
    }
    
}
When SwiftUI View is Equatable - questions...
 
 
Q