I am working on a school assignment for which I have to develop an iOS application in Swift 5. The application needs to utilize a web service (a web-API) and either file storage or user defaults.
I had chosen to develop a "QR code manager", in which users can create QR codes for a URL by setting a few design parameters, which are then sent to a generator API. This API (upon an OK request) returns an image in a specified format (PNG in my case).
I have a class with the URL and all the design properties of the QR code, which will also contain the image itself. Please see below code snippet for the class.
public class QRCode {
var bsId : Int?
var url : String?
var name: String?
var frame: Frame?
var logo: QrCodeLogo?
var marker: Marker?
var color : String?
var bgColor : String?
var image : Data?
init(data: [String:String]) {
self.url = data["url"]
self.frame = Frame.allCases.first(where: { $0.description == data["frame"] })
self.logo = QrCodeLogo.allCases.first(where: { $0.description == data["logo"] })
self.marker = Marker.allCases.first(where: { $0.description == data["marker"] })
self.bgColor = data["backGroundColor"]
self.color = data["colorLight"]
}
init(json: String) {
// todo
}
}
extension QRCode {
func toDict() -> [String:Any] {
var dict = [String:Any]();
let otherSelf = Mirror(reflecting: self);
for child in otherSelf.children {
if let key = child.label {
dict[key] = child.value;
}
}
return dict;
}
}
All the properties are nullable for ease of development, the class will be further refactored once I have successfully implemented everything.
I have tried various methods I found all over the internet, one of which can be found in the class extension. The function toDict()
translates the object properties and their values to a Dictionary
object of type [String:Any]
. However, I read that when the Any
datatype is encoded and then decoded, Swift cannot determine which complex datatype the decoded data is supposed to be, effectively rendering the data either meaningless or unusable.
Another method I found was through extending the Codable
-protocol in the class. As far as I am aware however, Codable
only accepts primitive datatypes.
Please find below my currently written code for file storage handling. It is not complete yet, but I felt it was a good start and might help in this question.
class StorageManager {
fileprivate let filemanager: FileManager = FileManager.default;
fileprivate func filePath(forKey key: String) -> URL? {
guard let docURL = filemanager.urls(for: .documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).first else {
return nil;
}
return docURL.appendingPathComponent(key);
}
func writeToStorage(identifier: String, data: QRCode) -> Void {
guard let path = filePath(forKey: identifier) else {
throw ApplicationErrors.runtimeError("Something went wrong writing the file to storage");
}
let dict = data.toDict();
// TODO:: Implement
}
func readFromStorage(identifier: String) -> Any {
// TODO:: Implement
return 0;
}
func readAllFromStorage() throws -> [URL] {
let docsURL = filemanager.urls(for: .documentDirectory, in: .userDomainMask)[0];
do {
let fileURLs = try filemanager.contentsOfDirectory(at: docsURL, includingPropertiesForKeys: nil);
return fileURLs;
} catch {
throw ApplicationErrors.runtimeError("Something went wrong retrieving the files from \(docsURL.path): \(error.localizedDescription)");
}
}
}
I am very new to Swift and I am running stuck on file storage. Is there any way I could store instances of this class in file storage in such a way that I could reïnstantiate this class when I retrieve the data?
Thanks in advance! Please do not hesitate to ask any questions if there are any.
Based on matt's comment, please find below the code snippets of the Marker
, Frame
, and QrCodeLogo
enums.
The Frame
enum:
public enum Frame: String, CaseIterable {
case noFrame
case bottomFrame
case bottomTooltip
case topHeader
static var count: Int { return 4 }
var description: String {
switch self {
case .noFrame:
return "no-frame"
case .bottomFrame:
return "bottom-frame"
case .bottomTooltip:
return "bottom-tooltip"
case .topHeader:
return "top-header"
}
}
}
The QrCodeLogo
enum:
public enum QrCodeLogo: String, CaseIterable {
case noLogo
case scanMe
case scanMeSquare
static var count: Int { return 3 }
var description: String {
switch self {
case .noLogo:
return "no-logo"
case .scanMe:
return "scan-me"
case .scanMeSquare:
return "scan-me-square"
}
}
}
The Marker
enum:
public enum Marker: String, CaseIterable {
case version1
case version2
case version3
case version4
case version5
case version6
case version7
case version8
case version9
case version10
case version11
case version12
case version13
case version15
case version16
static var count: Int { return 15 }
var description: String {
switch self {
case .version1:
return "version1"
case .version2:
return "version2"
case .version3:
return "version3"
case .version4:
return "version4"
case .version5:
return "version5"
case .version6:
return "version6"
case .version7:
return "version7"
case .version8:
return "version8"
case .version9:
return "version9"
case .version10:
return "version10"
case .version11:
return "version11"
case .version12:
return "version12"
case .version13:
return "version13"
case .version15:
return "version15"
case .version16:
return "version16"
}
}
}
All the above enums contain the valid design options for the API I use. They serve as an input restriction to prevent "invalid parameter"-errors from occurring.
Hopefully, this clears things up.
Thanks again!
A type can conform to Codable provided all its properties conform to Codable. All of your properties do conform to Codable except for the enums, and they will conform to Codable if you declare that they do. Thus, this simple sketch of your types compiles:
public enum Frame: String, Codable {
case noFrame
case bottomFrame
case bottomTooltip
case topHeader
}
public enum QrCodeLogo: String, Codable {
case noLogo
case scanMe
case scanMeSquare
}
public enum Marker: String, Codable {
case version1
case version2
case version3
case version4
case version5
case version6
case version7
case version8
case version9
case version10
case version11
case version12
case version13
case version15
case version16
}
public class QRCode : Codable {
var bsId : Int?
var url : String?
var name: String?
var frame: Frame?
var logo: QrCodeLogo?
var marker: Marker?
var color : String?
var bgColor : String?
var image : Data?
}
There are many, many other things about your code that could be improved.
You don't need CaseIterable or description
for anything. Now that your type is Codable, you can use it to retrieve the values from JSON directly, automatically. If the names of your enum cases do not match the corresponding JSON keys, just make a CodingKey nested enum to act as a bridge.
In other words, being Codable makes your type both populatable directly from the JSON and serializable to disk.