Contents of .sheet() are being redrawn after dismiss and not presenting

Use Case

If you have a ContentView that displays a pausable view based on a

@State private var presentSheet = false

property that is also used to present a

.sheet(isPresented: $presentSheet)

if:

  • the property is sent to the PausableView(isPaused: presentSheet) as a normal property, the body of the ContentView and the body of the sheet is being redrawn when the sheet is dismissed
  • the property is sent to the PausableView(isPaused: $presentSheet) as a @Binding, the body of the ContentView and the body of the sheet is NOT redrawn when the sheet is dismissed

Is this normal behavior?

The ContentView body makes sense to change, but is the sheet's body also supposed to be redrawn when the sheet is not presenting anymore after dismiss?

Sample Project

A sample project created in Xcode 13 is available here: https://github.com/clns/SwiftUI-sheet-redraw-on-dismiss. I noticed the same behavior on iOS 14 and iOS 15.

There are also 2 animated gifs showing the 2 different behaviors in the GitHub project.

The relevant code is in ContentView.swift:

import SwiftUI

struct DismissingView: View {
    @Binding var isPresented: Bool
    
    var body: some View {
        if #available(iOS 15.0, *) {
            print(Self._printChanges())
        } else {
            print("DismissingView: body draw")
        }
        return VStack {
            Button("Dismiss") { isPresented.toggle() }
            Text("Dismissing Sheet").padding()
        }.background(Color.white)
    }
}

struct PausableView: View {
    var isPaused: Bool
//    @Binding var isPaused: Bool
    
    private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    @State private var counter = 0
    
    var body: some View {
        Text("Elapsed seconds: \(counter)")
            .onReceive(timer) { _ in
                counter += isPaused ? 0 : 1
            }
    }
}

struct ContentView: View {
    @State private var presentSheet = false
    
    var body: some View {
        if #available(iOS 15.0, *) {
            print(Self._printChanges())
        } else {
            print("ContentView: body draw")
        }
        return VStack{
            Button("Show Sheet") { presentSheet.toggle() }
            Text("The ContentView's body along with the .sheet() is being redrawn immediately after dismiss, if the @State property `presentSheet` is used anywhere else in the view - e.g. passed to `PausableView(isPaused:presentSheet)`.\n\nBut if the property is passed as a @Binding to `PausableView(isPaused:$presentSheet)`, the ContentView's body is not redrawn.").padding()
            PausableView(isPaused: presentSheet)
//            PausableView(isPaused: $presentSheet)
        }
        .sheet(isPresented: $presentSheet) {
            DismissingView(isPresented: $presentSheet)
                .background(BackgroundClearView()) // to see what's happening under the sheet
        }
    }
}

Is this normal behavior?

The ContentView body makes sense to change, but is the sheet's body also supposed to be redrawn when the sheet is not presenting anymore after dismiss?

I do not understand what your term redraw means, but if it means body being evaluated, the answer would be:

You should not care about that.

The property body would be called at any time when the runtime of SwiftUI thinks it is needed, your code should not depend on when or how may times it may be called. It is sort of implementation details of SwiftUI, and may be changed at any time in the future.

Contents of .sheet() are being redrawn after dismiss and not presenting
 
 
Q