ioscore-dataswiftuiswift-optionals

Why the if != nil works only sometimes during the app being used?


I'm stuck with an issue and need your help. I'm working on an app to manage events and when app loads, all the posts are displayed as expected both with or without images into the post. If I add a new post that contains an image it saves correctly and go back to the scrollview displaying the new post also if I edit any item which have an image and keep the image when saving or add an image into the post that had no image before editing it saves fine and post is displayed immediately into the scrollView. The issue is if I try to create a new post without an image or if I edit a post removing the image and saving, app crashes due to unwrapping option but the weird is that I'm checking nil value into the point of the scrollview where the image is displayed.

ScrollView {
            VStack(spacing: 15){
                ForEach(items, id: \.self) { item in
                    VStack(spacing: 15) {
                        if item.pic != nil {
                            
                            ZStack(alignment: Alignment(horizontal: .trailing, vertical: .top)) {
                                
                                Image(uiImage: UIImage(data: item.pic!)!)
                                    .resizable()
                                    .aspectRatio(contentMode: .fill)
                                    .cornerRadius(15)
                            }
                            .padding()
                            .opacity(1)
                        }

Any ideas ? I'll really appreciate your support.


Solution

  • You have two force unwraps here. Your test for != nil only protects the first; The attempt to access the item.pic data. But, what if the conversion of that data to UIImage fails? - The failable initialiser will return nil, which you then force unwrap - which will lead to a crash.

    A safer approach is to avoid all force unwraps. I would add a computed property to your model:

    extension Item {
        var image: Image? {
            guard let data = self.pic, let uiImage = UIImage(data: data) else {
                return nil
            }
            return Image(uiImage: uiImage)
        }
    }
    

    Then you can just say:

    ScrollView {
        VStack(spacing: 15){
            ForEach(items, id: \.self) { item in
                VStack(spacing: 15) {
                if let image = item.image {
                    ZStack(alignment: Alignment(horizontal: .trailing, vertical: .top)) {
                        image
                         .resizable()
                         .aspectRatio(contentMode: .fill)
                         .cornerRadius(15)
                     }
                      .padding()
                      .opacity(1)
                    }
                }
            }
        }
    }
    

    You could also encapsulate the whole ZStack in a View subclass to simplify things further

    Rather than returning an optional Image you could return a placeholder image; it's up to you.