'SwiftUI : How I can delete the UIimage in the View saved as Codable?

I tried to save the image in my memo with as UIImage as Codable protocol.

My code can be a little bit longer, but please refer to it.

1. first one is the NewMemoView that I can make my memo with ImagePicker.

import SwiftUI

struct NewMemoView: View {
    
    let folder : FolderModel
    let memo : MemoModel?
    
    @State private var items : [Any] = []
    
    @EnvironmentObject var vm : MemoViewModel
    @Environment(\.presentationMode) var presentationMode
    
    @State private var title : String = ""
    @State private var content : String = ""
    @State private var color : Color = .mint
    @State private var showAlert : Bool = false
    @State private var pickedImportance : String = "Anytime 🐳"
    @State private var isEditMode : Bool = false
    @State private var isSecret : Bool = false
    @State private var password : String = ""
    @State private var showShareSheet : Bool = false
    @State private var showImagePicker : Bool = false
    @State private var selectedImage : UIImage?
    @State private var tappedImage : Bool = false
    @State private var showDeleteImageOption : Bool = false
    
    let importances = ["Anytime 🐳", "Normal ☀️", "Important 🔥"]
    
    var someImage : SomeImage? {
        guard let image = selectedImage else {return nil}
        return SomeImage(image: image)
    }
    
    init(memo : MemoModel?, folder : FolderModel) {
        self.folder = folder
        self.memo = memo
        if let memo = memo {
            _title = State(initialValue: memo.title)
            _content = State(initialValue: memo.content)
            _color = State(initialValue: memo.color)
            _pickedImportance = State(initialValue: memo.isImportant)
            _isEditMode = State(initialValue: true)
            _isSecret = State(initialValue: memo.isSecret)
            _password = State(initialValue: memo.password ?? "")
            _selectedImage = State(initialValue: memo.image?.image)
        }
    }
    
    var body: some View {
        Form {
            Section(header : Text("Title 🔖")) {
                TextField("Input the title", text: $title)
                    .autocapitalization(.none)
                    .disableAutocorrection(true)
            }
            
            Section(header : Text("Importance ✅")) {
                Picker("", selection: $pickedImportance) {
                    ForEach(importances, id: \.self) {
                        Text($0)
                    }
                }
                .pickerStyle(.segmented)
            }
            
            Section(header : Text("Content ✏️(총 \(content.count) 자)")) {
                TextEditor(text: $content)
                    .autocapitalization(.none)
                    .disableAutocorrection(true)
                    .frame(maxWidth : .infinity)
                    .frame(height : UIScreen.main.bounds.height * 0.25)

            }
            
            .onTapGesture {
                UIApplication.shared.closeKeyboard()
            }
            
            Section(header : Text("Image List 🌅")) {
                HStack {
                    if let selectedImage = selectedImage {
                        Image(uiImage: selectedImage)
                            .resizable()
                            .scaledToFill()
                            .frame(width : 50, height : 50)
                            .clipShape(RoundedRectangle(cornerRadius: 12))
                            .onTapGesture {
                                tappedImage.toggle()
                            }
                            .onLongPressGesture(perform: {
                                showDeleteImageOption.toggle()
                            })
                            .confirmationDialog(Text("Delete Image"), isPresented: $showDeleteImageOption, actions: {
                                Button(role : .destructive, action: {
                                    vm.deleteImage(folder: folder, memo: memo!, title: title, content: content, color: color, isImportant: pickedImportance, isSecret: isSecret, password: password)
                                }, label: {
                                    Text("Delete Image")
                                })
                            })
                            .sheet(isPresented: $tappedImage) {
                                ImageView(image: self.selectedImage!)
                            }
                    } else {
                        Text("You can upload only 'one' Image")
                            .foregroundColor(.gray)
                    }
                }
                .padding(8)
            }
            
            Section(header : Text("Memo Settings ⚙️")) {
                ColorPicker("Memo Color", selection: $color, supportsOpacity: false)
                Toggle(isOn: $isSecret, label: {
                    Text("Enable Secret Mode")
                })
                
                if isSecret {
                    HStack {
                        Text("Password : ")
                        Spacer()
                        TextField("Input", text: $password)
                    }
                }
            }
            
            Button(action: {   
                if isEditMode {
                    if let someImage = someImage {
                        if vm.checkMemoStatus(title: title, content: content) {
                            vm.editMemo(folder: folder, memo: memo!, title: title, content: content, color: color, isImportant: pickedImportance, isSecret: isSecret, password: password, image : someImage)
                            presentationMode.wrappedValue.dismiss()
                        } else {
                            vm.editMemo(folder: folder, memo: memo!, title: title, content: content, color: color, isImportant: pickedImportance, isSecret: isSecret, password: password, image : nil)
                            presentationMode.wrappedValue.dismiss()
                        }
                    } else {
                        self.showAlert.toggle()
                    }
                } else {
                    if vm.checkMemoStatus(title: title, content: content) {
                        if let someImage = someImage {
                            vm.addMemo(folder: folder, title: title, content: content, color: color, isImportant: pickedImportance, isSecret: isSecret, password: password, image: someImage)
                            presentationMode.wrappedValue.dismiss()
                        } else {
                            vm.addMemo(folder: folder, title: title, content: content, color: color, isImportant: pickedImportance, isSecret: isSecret, password: password, image: nil)
                            presentationMode.wrappedValue.dismiss()
                        }
                    } else {
                        self.showAlert.toggle()
                    }
                }
            }, label: {
                Text(isEditMode ? "Edit".uppercased() : "save".uppercased())
                    .fontWeight(.bold)
            })
            .alert(isPresented : $showAlert) {
                Alert(title: Text("Warning 🚫"), message: Text("Check your title and content, please."), dismissButton: .cancel())
            }
        }//form
        .navigationTitle("Add Memo 📒")
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button(action: {
                    share()
                }, label: {
                    Image(systemName: "square.and.arrow.up")
                })
                .disabled(!isEditMode)
            }
            ToolbarItem(placement: .navigationBarTrailing) {
                Button(action: {
                    showImagePicker.toggle()
                }, label: {
                    Image(systemName: "photo")
                })
                .sheet(isPresented: $showImagePicker) {
                    ImagePicker(image: $selectedImage)
                }
            }
        }
    }
}

extension NewMemoView {
    func share() {
        let content = memo?.content
        let image = memo?.image?.image
        let vc = UIActivityViewController(activityItems: [image, content], applicationActivities: nil)
        UIApplication.shared.windows.first?.rootViewController?.present(vc, animated: true)
    }
}

2. Second one is the viewModel for memos.

import SwiftUI
import Foundation

class MemoViewModel : ObservableObject {
    
    @Published var sortedMode : SortedMode?
    @Published var folders : [FolderModel] = [] {
        didSet {
            saveData()
        }
    }
    
    init() {
        getData()
    }
    
    let foldersKey = "Folders_key"
    
    func saveData() {
        if let encodedData = try? JSONEncoder().encode(folders) {
            UserDefaults.standard.set(encodedData, forKey: foldersKey)
        }
    }
    
    func getData() {
        guard let data = UserDefaults.standard.data(forKey: foldersKey),
              let savedData = try? JSONDecoder().decode([FolderModel].self, from: data)
        else {return}
        self.folders = savedData
    }
    
    func addNewFolder(folderName : String) {
        let newFolder = FolderModel(folderName: folderName)
        folders.append(newFolder)
    }
    
    func deleteFolder(folder : FolderModel) {
        if let index = folders.firstIndex(where: {$0.id == folder.id}) {
            folders.remove(at: index)
        }
    }
    
    func deleteFolder(indexSet : IndexSet) {
        folders.remove(atOffsets: indexSet)
    }
    
    func checkFolderNameCount(folderName : String) -> Bool {
        if folderName.count < 3 {
            return false
        } else {
            return true
        }
    }
    
    func checkMemoStatus(title : String, content : String) -> Bool {
        if title.count == 0 || content.count == 0 {
            return false
        } else {
            return true
        }
    }
    
    func addMemo(folder : FolderModel, title : String, content : String, color : Color, isImportant : String, isSecret : Bool, password : String, image : SomeImage?) {
        if let index = folders.firstIndex(where: {$0.id == folder.id}) {
            let newMemo = MemoModel(title : title, content : content, color : color, isImportant: isImportant, isSecret: isSecret, password: password, image: image)
            folders[index].memo.append(newMemo)
        }
    }
    
    func editMemo(folder : FolderModel, memo : MemoModel, title : String, content : String, color : Color, isImportant : String, isSecret : Bool, password : String, image : SomeImage?) {
        if let index = folders.firstIndex(where: {$0.id == folder.id}) {
            if let index1 = folders[index].memo.firstIndex(where: {$0.id == memo.id}) {
                folders[index].memo[index1] = memo.editMemo(title: title, content: content, color: color, isImportant: isImportant, isSecret: isSecret, password: password, image : image)
            }
        }
    }
    
    func deleteMemo(folder : FolderModel, memo : MemoModel) {
        if let index = folders.firstIndex(where: {$0.id == folder.id}) {
            if let index1 = folders[index].memo.firstIndex(where: {$0.id == memo.id}) {
                folders[index].memo.remove(at: index1)
            }
        }
    }
    
    func deleteImage(folder : FolderModel, memo : MemoModel, title : String, content : String, color : Color, isImportant : String, isSecret : Bool, password : String) {
        if let index = folders.firstIndex(where: {$0.id == folder.id}) {
            if let index1 = folders[index].memo.firstIndex(where: {$0.id == memo.id}) {
                folders[index].memo[index1] = memo.deleteImage(title: title, content: content, color: color, isImportant: isImportant, isSecret: isSecret, password: password)
            }
        }
    }
    
    func checkPassword(memo : MemoModel, password : String) -> Bool {
        if memo.password == password {
            return true
        } else {
            return false
        }
    }
    
    func sortedMemos(folder : FolderModel) {
        if let sortedMode = sortedMode {
            switch sortedMode {
            case .moreImportant:
                if let index = folders.firstIndex(where: {$0.id == folder.id}) {
                    folders[index].memo.sort(by: {$0.isImportant > $1.isImportant})
                }
            case .faster:
                if let index = folders.firstIndex(where: {$0.id == folder.id}) {
                    folders[index].memo.sort(by: {$0.timeStamp < $1.timeStamp})
                }
            case .later:
                if let index = folders.firstIndex(where: {$0.id == folder.id}) {
                    folders[index].memo.sort(by: {$0.timeStamp > $1.timeStamp})
                }
            case .lessImportant:
                if let index = folders.firstIndex(where: {$0.id == folder.id}) {
                    folders[index].memo.sort(by: {$0.isImportant > $1.isImportant})
                }
            }
        }
    }
}

enum SortedMode {
    case moreImportant, faster, later, lessImportant
}

3. Last one is the codable model for this memo App


import SwiftUI
import Foundation

struct FolderModel : Identifiable, Codable {
    var id = UUID()
    var folderName : String
    var memo : [MemoModel] = []
}

struct MemoModel : Identifiable, Codable {
    var id = UUID()
    var title : String = "제목 없음"
    var content : String
    var color : Color = .gray
    var timeStamp : String = Date().formatted(date: .numeric, time: .omitted)
    var isImportant : String
    var isSecret : Bool = false
    var password : String?
    var image : SomeImage?

    func editMemo(title : String, content : String, color : Color, isImportant : String, isSecret : Bool, password : String, image : SomeImage?) -> MemoModel {
        return MemoModel(id: id, title: title, content: content, color: color, isImportant: isImportant, isSecret: isSecret, password: password, image: image)
    }
    
    func deleteImage(title : String, content : String, color : Color, isImportant : String, isSecret : Bool, password : String) -> MemoModel {
        return MemoModel(id: id, title: title, content: content, color: color, isImportant: isImportant, isSecret: isSecret, password: password, image: nil)
    }
}

struct SomeImage : Identifiable, Codable {
    var id = UUID()
    var image: UIImage?

    init(image: UIImage) {
        self.image = image
    }

    enum CodingKeys: CodingKey {
        case data
        case scale
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let scale = try container.decode(CGFloat.self, forKey: .scale)
        let data = try container.decode(Data.self, forKey: .data)
        self.image = UIImage(data: data, scale: scale)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        if let image = self.image {
            try container.encode(image.pngData(), forKey: .data)
            try container.encode(image.scale, forKey: .scale)
        }
    }
}

I will summarize my point!

  1. When I try to delete the image by deleteMemo, giving nil to image, the NewMemoView is shut down. I don't know why, but it it doing.

  2. When I tap the save button with image, there is some lagging, how I can handle this situation? I think maybe that most of image has bigger capacity than text.

Thanks!



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source