App crashes when setting variable to nil

I have been experiencing a problem that I can't seem to figure out how it's happening or how to fix it.

When then view below appears, the app crashes and shows this message:
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1a2120fac)

If I remove the line that sets color to nil the view appears without any crashes.

Code Block Swift
...
@Binding var color: Color?
var body: some View {
NavigationView { ... }
.onAppear {
switch background {
case .color:
color = nil // error not occurring when this line removed
...
}
}
}

I have looked up the error and worked out its about nil and optionals, but I don't know why setting the variable to nil doesn't work.

Has anyone got any ideas as to why this is happening and how to fix it?

Thanks is advance.
Answered by OOPer in 638598022
I cannot explain why, but if I replace lines 131 to 140 as follows, the error does not occur.
Code Block
ColorPicker("Change color", selection: Binding<Color> {
color ?? Color(.systemBackground)
} set: {
color = $0
})

Please try.
Sorry, but I cannot reproduce the same issue with your code.

Can you create a minimized project which can reproduce the issue and show full code of it?
Could you show the complete code so we can replicate the problem ?
Where do you use color elsewhere in code ?

Here is an example with optional in a Binding which works

Code Block
struct DestViewStateBinding: View {
@Binding var theStateBinding: Int
@Binding var someOpt: Int?
var body: some View {
if theStateBinding < 50 { theStateBinding += 10 ; someOpt = nil }
return Text("Hello, Destination! \(theStateBinding)")
}
}
struct ContentView: View {
var contentNoState : Int = 1
@State var contentState : Int = 1
@State var contentState2 : Int? = 1
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DestViewStateBinding(theStateBinding: $contentState, someOpt: $contentState2)) {
Text("Press on me with Binding")
}.buttonStyle(PlainButtonStyle())
}
}
}
}

It turns out the problem is linked with a ColorPicker.
Code Block Swift
switch background {
case .color:
ColorPicker("Change color", selection: Binding($color)!) // only works when color is not nil
...
}

I tried adding a custom Binding for when color is nil, which still doesn't work.
Code Block Swift
if color != nil {
ColorPicker("Change color", selection: Binding($color)!)
} else {
ColorPicker("Change color", selection: Binding<Color> {
Color(.systemBackground)
} set: {
color = $0
})
}

Could you please post the complete code so that we can test ?
Code Block Swift
struct TestView: View {
private enum BackgroundType: String, CaseIterable, Identifiable {
var id: String { self.rawValue }
case color
case image
}
@State private var backgroundType: BackgroundType = .color
@Binding var color: Color?
@Binding var image: UIImage?
var body: some View {
NavigationView {
Form {
Picker("Background", selection: $backgroundType) {
ForEach(BackgroundType.allCases) {
Text($0.rawValue.capitalized).tag($0)
}
}
.pickerStyle(SegmentedPickerStyle())
switch backgroundType {
case .color:
if color != nil {
ColorPicker("Change color", selection: Binding($color)!) // error
} else {
ColorPicker("Change color", selection: Binding<Color> {
Color(.systemBackground)
} set: {
color = $0
})
}
case .image:
Image(uiImage: image!)
}
}
}
.onAppear {
if image == nil {
backgroundType = .color
} else {
backgroundType = .image
}
}
.toolbar {
Button("Save") {
switch backgroundType {
case .color:
image = nil
case .image:
color = nil // error
}
}
}
}
}

Could you show as well the @main section ?

Should be like :

Code Block
import SwiftUI
@main
struct Project_BindingApp: App {
var body: some Scene {
WindowGroup {
TestView(color: UIColor.green, image: UIImage(systemName: "checkmark.circle"))
}
}
}

The color and image are being loaded from UserDefaults and the documentDirectory using this:
Code Block Swift
extension UserDefaults {
func color(forKey defaultName: String) -> Color? {
if let data = data(forKey: defaultName) {
if let color = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) {
return Color(color)
}
}
return nil
}
}
extension URL {
func loadImage(_ image: inout UIImage?) {
if let data = try? Data(contentsOf: self), let loaded = UIImage(data: data) {
image = loaded
} else {
image = nil
}
}
}

And then in the main view:
Code Block Swift
private func load() {
if let color = UserDefaults.standard.color(forKey: "backgroundColor") {
backgroundColor = color
} else {
imageURL.loadImage(&backgroundImage)
}
}

which is called when .onAppear(perform: load),
and where backgroundColor and backgroundImage are optional @State variables that are passed into TestView.
Haven't you got any file for the App struct, as I showed in a previous post ?

It is pretty hard from the pieces of code to get the whole picture and see where everythging fits.

Could you post all the complete project files, that would be much easier.
The app struct doesn't have anything in.
It's ContentView that loads and passes the color to TestView.
Code Block Swift
struct ContentView: View {
@State private var backgroundColor: Color?
@State private var backgroundImage: UIImage?
private func load() { ... }
var body: some View {
...
.sheet(...) {
TestView(color: $backgroundColor, image: $backgroundImage)
}
.onAppear(perform: load)
}
}

Please ! Post the real and complete code, with all required files, not edited with a lot of ellipsis. The goal is to try to reproduce and not spend hours rebuilding what you did not show.

If you cannot or do not want, let's stop here.
Here it is in one bit.

Code Block Swift
import SwiftUI
extension URL {
func loadImage(_ image: inout UIImage?) {
if let data = try? Data(contentsOf: self), let loaded = UIImage(data: data) {
image = loaded
} else {
image = nil
}
}
func saveImage(_ image: UIImage?) {
if let image = image {
if let data = image.jpegData(compressionQuality: 1.0) {
try? data.write(to: self)
}
} else {
try? FileManager.default.removeItem(at: self)
}
}
}
extension UserDefaults {
func color(forKey defaultName: String) -> Color? {
if let data = data(forKey: defaultName) {
if let color = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) {
return Color(color)
}
}
return nil
}
func set(_ value: Color?, forKey defaultName: String) {
if let color = value {
if let data = try? NSKeyedArchiver.archivedData(withRootObject: UIColor(color), requiringSecureCoding: false) {
set(data, forKey: defaultName)
}
} else {
removeObject(forKey: defaultName)
}
}
}
struct ContentView: View {
@State private var showingTest = false
@State private var backgroundImage: UIImage?
@State private var backgroundColor: Color?
private var imageURL: URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0].appendingPathComponent("backgroundImage")
}
private func load() {
if let color = UserDefaults.standard.color(forKey: "backgroundColor") {
backgroundColor = color
} else {
imageURL.loadImage(&backgroundImage)
}
}
    var body: some View {
ZStack {
GeometryReader { geometry in
if let image = backgroundImage {
Image(uiImage: image)
.resizable()
.scaledToFill()
.frame(width: geometry.size.width, height: geometry.size.height)
} else if let color = backgroundColor {
color
} else {
Color(.systemBackground)
}
}
.ignoresSafeArea()
Button("Test") {
showingTest = true
}
}
.onAppear(perform: load)
.sheet(isPresented: $showingTest) {
TestView(image: $backgroundImage, color: $backgroundColor)
}
    }
}
struct TestView: View {
private enum BackgroundType: String, CaseIterable, Identifiable {
var id: String { self.rawValue }
case color
case image
}
@Environment(\.presentationMode) private var presentationMode
@State private var backgroundType: BackgroundType = .color
@State private var showingImagePicker = false
@Binding var image: UIImage?
@Binding var color: Color?
private var imageURL: URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0].appendingPathComponent("backgroundImage")
}
var body: some View {
NavigationView {
Form {
Section(header: HStack {
Text("BACKGROUND")
Spacer()
HStack {
Picker("Types of background", selection: $backgroundType) {
ForEach(BackgroundType.allCases) {
Text($0.rawValue.capitalized).tag($0)
}
}
.pickerStyle(SegmentedPickerStyle())
.frame(width: 120)
}
}) {
switch backgroundType {
case .color:
if color != nil {
ColorPicker("Change color", selection: Binding($color)!)
.foregroundColor(.accentColor)
} else {
ColorPicker("Change color", selection: Binding<Color> {
Color(.systemBackground)
} set: {
color = $0
})
}
case .image:
HStack {
Button("Change image") {
showingImagePicker = true
}
.sheet(isPresented: $showingImagePicker) {
PhotoPicker(image: $image)
.ignoresSafeArea()
}
Spacer()
Image(uiImage: image ?? UIImage(systemName: "questionmark.diamond")!)
.resizable()
.scaledToFill()
.frame(width: 40, height: 40)
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}
}
}
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Save") {
presentationMode.wrappedValue.dismiss()
switch backgroundType {
case .color:
image = nil
case .image:
color = nil
}
DispatchQueue.main.async {
UserDefaults.standard.set(color, forKey: "backgroundColor")
imageURL.saveImage(image)
}
}
}
}
}
.onAppear {
if image == nil {
backgroundType = .color
} else {
backgroundType = .image
}
}
}
}
struct PhotoPicker: UIViewControllerRepresentable {
class Coordinator: PHPickerViewControllerDelegate {
let parent: PhotoPicker
init(_ parent: PhotoPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
parent.presentationMode.wrappedValue.dismiss()
if let itemProvider = results.first?.itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self) {
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
DispatchQueue.main.async {
guard let self = self, let image = image as? UIImage else { return }
self.parent.image = image
}
}
}
}
}
@Environment(\.presentationMode) var presentationMode
@Binding var image: UIImage?
func makeUIViewController(context: Context) -> some UIViewController {
var configuration = PHPickerConfiguration(photoLibrary: .shared())
configuration.filter = .images
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}

The error occurs when the segmented picker it set to Image and the sheet is dismissed.
Great I will test it.

I'm just surprised you have no file with @main to give the app an entry point…
There is but it's just the basic template one. I forgot to include that.
@BabyJ

Here it is in one bit.

Thanks for showing the full code.

The error occurs when the segmented picker it set to Image and the sheet is dismissed.

Can you clarify the right sequence to reproduce the issue and show us the full message of the error?
  1. Open the sheet

  2. Choose a color

  3. Change the segmented picker to image

  4. (optional) Choose an image

  5. Dismiss the sheet

It happens randomly sometimes.

The ColorPicker is also a bit buggy.
When choosing the color the small preview doesn't change but the background does.
Probably because of the custom Binding's get closure.
App crashes when setting variable to nil
 
 
Q