How do i save an image as a userdefault in swift?

159 Views Asked by At

problem image

The code is:

@IBAction func CheckBoxTuesdayButtonPressed(_ sender: Any) {
        
        
        
        if CheckBoxTuesday.image == UIImage(imageLiteralResourceName: "CheckmarkButtonPressed"){
            
            CheckBoxTuesday.image = UIImage(imageLiteralResourceName: "CheckBoxButton")
               
            
        }
        else{
            
            CheckBoxTuesday.image = UIImage(imageLiteralResourceName: "CheckmarkButtonPressed")
            UserDefaults.standard.setValue(CIImage.self, forKey: "TuesdayChecked")
        }

I tried the Userdefault line of code but it said i couldnt convert it to NSuserdefault.

3

There are 3 best solutions below

0
On

tl;dr

Rather than saving the image associated with the checkbox, you should save a model object, some representation that captures which check boxes have been selected.


We often draw a distinction between the “model” (some structure that indicates which check boxes have been checked) from the “view” (which images are shown in which UI controls). So, you don’t really want to save the image in persistent storage at all, but rather some model structure that indicates which switches are at what state. Likewise, when toggling a control, you toggle the representation in your model, and then update the UI (and persistent storage) accordingly.

For example, it appears that you have switches for each of the days of the week. That might call for some model object that represented which switches are turned on and off. An OptionSet seems like a logical candidate:

struct Days: OptionSet {
    let rawValue: Int

    static let sunday     = Days(rawValue: 1 << 0)
    static let monday     = Days(rawValue: 1 << 1)
    static let tuesday    = Days(rawValue: 1 << 2)
    static let wednesday  = Days(rawValue: 1 << 3)
    static let thursday   = Days(rawValue: 1 << 4)
    static let friday     = Days(rawValue: 1 << 5)
    static let saturday   = Days(rawValue: 1 << 6)

    static let none: Days = []
    static let all: Days  = [.sunday, .monday, .tuesday, .wednesday, .thursday, .friday, .saturday]
}

And you might have some method on this type, Days, to toggle a particular day on and off:

extension Days {
    mutating func toggle(_ day: Days) {
        if contains(day) {
            remove(day)
        } else {
            insert(day)
        }
    }
}

Then, you might have a property to store your model object to reflect what days are checked:

var selectedDays: Days = .none

And then the @IBAction might do something like:

@IBAction func didTapTuesday(_ sender: Any) {
    selectedDays.toggle(.tuesday)
    UserDefaults.standard.set(selectedDays.rawValue, forKey: "days")
    tuesdayCheckBox.image = selectedDays.contains(.tuesday) ? .checked : .unchecked
}

(Forgive my changing of the names of this function and the properties as convention is to start method and variable names with a lowercase letter. We only use uppercase letters for type names, but methods and properties always start with a lowercase letter. But use whatever method/property names you want.)

Anyway, the images checked and unchecked might be defined like so:

extension UIImage {
    static var checked = UIImage(imageLiteralResourceName: "CheckmarkButtonPressed")
    static var unchecked = UIImage(imageLiteralResourceName: "CheckBoxButton")
}

And, of course, in viewDidLoad, you might call a function that fetches the value and updates all the controls:

func fetchDays() {
    let value = UserDefaults.standard.integer(forKey: "days")
    selectedDays = Days(rawValue: value)
    sundayCheckBox.image    = selectedDays.contains(.sunday)    ? .checked : .unchecked
    mondayCheckBox.image    = selectedDays.contains(.monday)    ? .checked : .unchecked
    tuesdayCheckBox.image   = selectedDays.contains(.tuesday)   ? .checked : .unchecked
    wednesdayCheckBox.image = selectedDays.contains(.wednesday) ? .checked : .unchecked
    thursdayCheckBox.image  = selectedDays.contains(.thursday)  ? .checked : .unchecked
    fridayCheckBox.image    = selectedDays.contains(.friday)    ? .checked : .unchecked
    saturdayCheckBox.image  = selectedDays.contains(.saturday)  ? .checked : .unchecked
}

So all of this cuts the Gordian knot, eliminating the very idea of storing the image in UserDefaults at all.

That having been said, I personally would not store any model data in UserDefaults (not even just this Int raw value). I would write it to persistent storage (in iOS 17, I might use Swift Data as outlined in WWDC 2023’s Meet SwiftData). Or perhaps that’s overkill, then I might make Days a Codable type and write it to a JSON file in persistent storage.

Use UserDefaults, as shown in my code snippets above, if you really must. It is, admittedly, very convenient. But one should recognize that UserDefaults is intended for defaults/preferences, not for model data, and we would generally use one of these other storage mechanisms for model data.


Note, my model structure, Days, which is an OptionSet, is just one example of a possible model structure. There are lots of possible permutations on the idea. You might have a series of Booleans. Or you may get have some other structure to capture you model data (e.g., a Set, an Array, a custom struct, etc.). But the idea is the same: Have some model type that represents the logical data. But we would not generally use UIImage (unless the user was picking custom images from their photo library or camera).

2
On

What you are trying to do is not recommended. You are only supposed to save small snippets like user preferences.

If you are determined to do it anyway, you need to convert your data to one of the small number of "property list types" that UserDefaults supports.

They are, using their Objective-C names (With the Swift name in parens)

NSData (Data), NSString (String), NSNumber (Still NSNumber), NSDate (Date), NSArray (Array), NSDictionary (Dictionary)

(NSNumber is an object wrapper for a scalar value. UserDefaults has helper functions that let you read/write scalar values like Int, Float, and Double.)

In your case you'd have to convert your image to Data and the save the Data. (But don't do that.)

Edit:

Also note that there are various problems with your code. The line that attempts to save the image tries to save CIImage.self which is totally wrong.

In your case it looks like you are trying to save the state of a user's checkbox selection. Saving an image to remember a user's selection is silly. Create an enum with an Int raw value and save the rawValue to UserDefaults.

And your code that figures out what to save makes no sense either. That code is riddled with errors.

6
On

Storing images in UserDefaults is not recommended, as it can reduce app performance and increase the size of the app's user defaults database. However, if you still need to store an image in UserDefaults, you can convert it to a base64 string and store that instead.

import UIKit

extension UIImage {
    func toBase64() -> String? {
        guard let data = pngData() else { return nil }
        return data.base64EncodedString()
    }
}

func saveBase64Image(image: UIImage, key: String) {
    if let base64Image = image.toBase64() {
        UserDefaults.standard.set(base64Image, forKey: key)
    }
}