Up until now, it was possible to check in build scripts via the ENABLE_PREVIEWS environment variable whether a build for a SwiftUI preview is being executed.
In addition, it was also possible to conditionally compile code for SwiftUI or exclude it from compilation using this variable.
This no longer works with Xcode 16 and the new SwiftUI Preview compilation!
There is still the option to switch to the old system with the "Use Legacy Previews Execution" setting, but as stated in the release notes, this option will be removed at some point.
Which brings us back to the old problem of not being able to exclude build scripts from preview builds and not being able to prevent certain code from being compiled / add special code for SwiftUI previews.
This is a terrible situation, especially for more complex projects in which precisely the points mentioned are important for the build process.
I seriously hope Apple provides us with another environment variable for SwiftUI preview builds that we can use.
Post
Replies
Boosts
Views
Activity
Scrolling to a SwiftUI view with the onAppear method can cause state changes of published values from an ObservableObject to be lost and a view to be displayed in an incorrect state.
Here is a demo showing the problem:
struct ContentView: View {
@StateObject private var viewModel = ContentViewModel()
@Namespace private var state2ID
var body: some View {
ScrollViewReader { scrollProxy in
ScrollView(.vertical) {
VStack(spacing: 15) {
if viewModel.state2 {
VStack {
Text("State2 is set")
}
.id(state2ID)
.onAppear {
print("scrolling")
withAnimation {
scrollProxy.scrollTo(state2ID)
}
}
}
VStack(spacing: 0) {
Text("State1: \(viewModel.state1)")
Text("State1 changes from 'false -> true -> false' when the button is pressed.")
.font(.footnote)
}
Button("Toggle States") {
viewModel.toggleStates()
}
.buttonStyle(.bordered)
Color.teal
.frame(height: 900)
}
.padding()
}
}
}
}
@MainActor
final class ContentViewModel: ObservableObject {
@Published private(set) var state1 = false
@Published private(set) var state2 = false
private var stateToggle = false
func toggleStates() {
Task { @MainActor in
state1 = true
defer {
// This change never becomes visible in the view!
// state1 will be improperly shown as 'true' when this method returns while it actually is 'false'.
print("Resetting state1")
state1 = false
}
stateToggle.toggle()
if stateToggle {
withAnimation {
state2 = true
}
} else {
state2 = false
}
}
}
}
After pressing the button, “State1: true” is displayed in the view, which no longer corresponds to the actual value of the view model property, which is false.
Effectively, the change in the defer call of the toggleStates method is lost and the view is no longer in sync with the actual state values of the view model.
Tested with iOS 17.5 and 18.0 (devices + simulators)