Serialize enum struct that have cases with arguments - ios

I have an enum struct like that:
enum MyStruct {
case CanRegister(String, String)
case Register(String, String, String, String, String?)
case Login(String, String)
...
}
Is it possible to serialize / encode and decode struct like that so I can easily save states to NSUserDefaults?
I know how to serialize custom structs, even enums that are of type: enum MyStruct : String{}, but can I do that with that kind of structure and how?
Thanks

I assume that you want a general method here, rather than one hard-coded for your specific enum. I haven't worked it all the way through, but using Mirror is where you should start. Consider this as a hint rather than an answer...
(p.s. this is based on ideas from this article)
enum MyStruct {
case IntVal(Int)
case CanRegister(String, String)
case Register(String, String, String, String, String?)
case Login(String, String)
// ...
}
func prettyPrint(_ any: Any) -> String {
let m = Mirror(reflecting: any)
var a: String
switch m.displayStyle {
case .some(.tuple):
a = "Tuple"
case .some(.enum):
a = "Enum"
default:
return "\(m.displayStyle)"
}
a += " \(m.subjectType) = ("
for case let (label?, value) in m.children {
a += "\(label), \(value)"
}
return a + ")"
}
let ab = MyStruct.CanRegister("A", "B")
let i = MyStruct.IntVal(7)
prettyPrint(ab) // "Enum MyStruct = (CanRegister, ("A", "B"))"
prettyPrint(i) // "Enum MyStruct = (IntVal, 7)"

Related

Swift 5: saving complex objects to file storage

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.
Edit
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.

Add arguments in enum cases. getting error : 'Enum with raw type cannot have cases with arguments'

I would like to do something like that:
public enum MyEnum: String {
case tapOnSection(section: String) = "tap on \(section)"
}
var section: String = "funny section"
func someFuntion() {
print(MyEnum.tapOnSection(self.section))
}
Console: "tap on funny section"
Do you some if could I do something like that or some suggestions?
Thank you
You can add methods to enums:
public enum MyEnum {
case tapOnSection(section: String)
var description: String {
switch self {
case let tapOnSection(section: section):
return "tap on \(section)"
}
}
}
var funnySection: String = "funny section"
print(MyEnum.tapOnSection(funnySection).description)
An enum can't have both associated values (i.e. cases with arguments) and raw values. That's a limitation of the language at the moment.
The simplest way I can think of to approximate what you want is to create a computed property that replaces the raw value in some way. e.g. for your specific case you can do this:
enum MyEnum
{
case tapOnSection(section: String)
}
// Might as well implement CustomStringConvertible so you can do
// print(MyEnum.something) instead of print(MyEnum.something.description)
extension MyEnum: CustomStringConvertible
{
var description: String
{
switch self
{
case .tapOnSection(let section):
return "tap on \(section)"
// A switch case for each case in your enum
}
}
}
print(MyEnum.tapOnSection(section: "foo")) // prints "tap on foo"

How I can get rawValue from enum, if element is Any?

I have a variable of type Any?. I'm totally know what that variable is type of enum: String. How I can get rawValue something like:
var somevar: Any? = someValue
(somevar as ?????).rawValue
First of all sorry I misunderstood your question.
Yes it is possible and very EASY
it is beauty of swift
You have to add some extra step there
Step1 :
Add protocol
protocol TestMe {
var rawValueDesc: String {get}
}
Step 2 :
In your enum implement it
enum YourEnum:String,TestMe {
case one = "test"
case two = "test1"
var rawValueDesc: String {
return self.rawValue
}
}
Finally
var testdd:Any = YourEnum.one
if let finalValue = testdd as? TestMe {
print( finalValue.rawValueDesc)
}
Hope it is helpful to you
Assuming you have this defined somewhere in your or in imported module:
enum First: String {
case a, b
}
enum Second: String {
case c, d
}
In you module your should do something like this:
protocol StringRawRepresentable {
var rawValue: String { get }
}
extension First: StringRawRepresentable {}
extension Second: StringRawRepresentable {}
And here's your problem:
var somevar: Any? = someValue
let result = (somevar as? StringRawRepresentable)?.rawValue
If, for example, someValue == Second.c you gonna get "c" in result.
This approach will work, but you will have to extend all the possible types, otherwise as? casting will result in nil even if type has rawValue: String property.

Enum of structs in Swift 3.0

I am trying to create an enum of a struct that I would like to initialize:
struct CustomStruct {
var variable1: String
var variable2: AnyClass
var variable3: Int
init (variable1: String, variable2: AnyClass, variable3: Int) {
self.variable1 = variable1
self.variable2 = variable2
self.variable3 = variable3
}
}
enum AllStructs: CustomStruct {
case getData
case addNewData
func getAPI() -> CustomStruct {
switch self {
case getData:
return CustomStruct(variable1:"data1", variable2: SomeObject.class, variable3: POST)
case addNewData:
// Same to same
default:
return nil
}
}
}
I get the following errors:
Type AllStructs does not conform to protocol 'RawRepresentable'
I am assuming that enums cannot be used this way. We must use primitives.
It should be:
struct CustomStruct {
var apiUrl: String
var responseType: AnyObject
var httpType: Int
init (variable1: String, variable2: AnyObject, variable3: Int) {
self.apiUrl = variable1
self.responseType = variable2
self.httpType = variable3
}
}
enum MyEnum {
case getData
case addNewData
func getAPI() -> CustomStruct {
switch self {
case .getData:
return CustomStruct(variable1: "URL_TO_GET_DATA", variable2: 11 as AnyObject, variable3: 101)
case .addNewData:
return CustomStruct(variable1: "URL_TO_ADD_NEW_DATA", variable2: 12 as AnyObject, variable3: 102)
}
}
}
Usage:
let data = MyEnum.getData
let myObject = data.getAPI()
// this should logs: "URL_TO_GET_DATA 11 101"
print(myObject.apiUrl, myObject.responseType, myObject.httpType)
Note that upon Naming Conventions, struct should named as CustomStruct and enum named as MyEnum.
In fact, I'm not pretty sure of the need of letting CustomStruct to be the parent of MyEnum to achieve what are you trying to; As mentioned above in the snippets, you can return an instance of the struct based on what is the value of the referred enum.
I'm not commenting on the choice to use an enum here, but just explaining why you got that error and how to declare an enum that has a custom object as parent.
The error shows you the problem, CustomStruct must implement RawRepresentable to be used as base class of that enum.
Here is a simplified example that shows you what you need to do:
struct CustomStruct : ExpressibleByIntegerLiteral, Equatable {
var rawValue: Int = 0
init(integerLiteral value: Int){
self.rawValue = value
}
static func == (lhs: CustomStruct, rhs: CustomStruct) -> Bool {
return
lhs.rawValue == rhs.rawValue
}
}
enum AllStructs: CustomStruct {
case ONE = 1
case TWO = 2
}
A few important things that we can see in this snippet:
The cases like ONE and TWO must be representable with a Swift literal, check this Swift 2 post for a list of available literals (int,string,array,dictionary,etc...). But please note that in Swift 3, the LiteralConvertible protocols are now called ExpressibleByXLiteral after the Big Swift Rename.
The requirement to implement RawRepresentable is covered implementing one of the Expressible protocols (init?(rawValue:) will leverage the initializer we wrote to support literals).
Enums must also be Equatable , so you'll have to implement the equality operator for your CustomStruct base type.
Did you try conforming to RawRepresentable like the error is asking?
Using JSON representation should work for variable1 and variable3. Some extra work may be required for variable2.
struct CustomStruct: RawRepresentable {
var variable1: String
var variable2: AnyClass
var variable3: Int
init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8) else {
return nil
}
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
return nil
}
self.variable1 = (json["variable1"] as? String) ?? ""
self.variable2 = (json["variable2"] as? AnyClass) ?? AnyClass()
self.variable3 = (json["variable3"] as? Int) ?? 0
}
var rawValue: String {
let json = ["variable1": self.variable1,
"variable2": self.variable2,
"variable3": self.variable3
]
guard let data = try? JSONSerialization.data(withJSONObject: json, options: []) else {
return ""
}
return String(data: data, encoding: .utf8) ?? ""
}
}
According to the documentation:
If a value (known as a “raw” value) is provided for each enumeration case, the value can be a string, a character, or a value of any integer or floating-point type.
So yes, you cannot set a struct type to be enum's raw value.
In your case I would suggest using string as the enum raw value and some dictionary mapping these strings to CUSTOM_STRUCT type.
A bit late on the party but maybe useful for someone else. I would consider to simply use computed variables instead of a struct.
enum MyEnum {
case getData
case addNewData
var variable1: String {
switch self {
case .getData: return "data1"
case .addNewData: return "data2"
}
}
var variable2: Int {
switch self {
case .getData: return 1
case .addNewData: return 2
}
}
// ....
}
Usage:
let data = MyEnum.getData
print (data.variable1) // "data1"

Swift/iOS: How to use inout parameters in functions with AnyObject/Any or Pointers

I'm trying to write a function that takes a variable pointer and a descriptor/key and sets a new value for the variable. Ideally the pointer should be either of an object or a primitive, but I could also live with separate functions (or an additional parameter).
In my code I retrieve the new value from a database also using the key, but in the following example I simplified it with dummy values so that it can be used easily in a playground:
import UIKit
func setValue(inout object: AnyObject, key: String) {
switch key {
case "String":
object = "A String"
case "UIColor":
object = UIColor.whiteColor()
case "Bool":
object = true
default:
println("Unhandled key: \(key)")
}
}
var string: String = "Default String"
var color: UIColor = UIColor.blackColor()
var bool: Bool = false
setValue(&string, "String")
setValue(&color, "UIColor")
setValue(&bool, "Bool")
I get the following error:
"Cannot invoke 'setValue' with an argument list of type '(inout
String, key String)'"
I understand that I'm mixing Objects and Primitives here. I also tried to break it down and separate these into two functions, but even that fails:
func setValue(inout object: AnyObject, key: String) {
switch key {
case "UIColor":
object = UIColor.whiteColor()
default:
println("Unhandled key: \(key)")
}
}
var color: UIColor = UIColor.blackColor()
setValue(&color, "UIColor")
This also gives the same error:
"Cannot invoke 'setValue' with an argument list of type '(inout
UIColor, key String)'"
If I change 'AnyObject' to 'UIColor' it works, but the point of the function is, that it takes any variable type or at least any object type (I'd write a second function using "Any" for primitives then or add another parameter)
In Objective-C I was using pointers, transferring the approach to Swift also doesn't work, same result:
func setValue(object: UnsafeMutablePointer<AnyObject>, key: String) {
switch key {
case "String":
object.memory = "A String"
case "UIColor":
object.memory = UIColor.whiteColor()
case "Bool":
object.memory = true
default:
println("Unhandled key: \(key)")
}
}
Does anybody have an idea what I'm missing here? Any help is much appreciated!
Thanks!
Better you can create a generic method like below:
func setValue<T>(inout object:T, key: String) {
switch key {
case "String":
object = ("A String" as? T)!
case "UIColor":
object = (UIColor.whiteColor() as? T)!
case "Bool":
object = (true as? T)!
default:
println("Unhandled key: \(key)")
}
}
And calling will be like this:
setValue(&string, key: "String")
setValue(&color, key: "UIColor")
setValue(&bool, key: "Bool")
Hope it helps!
The right way to do this is to use overloading, and letting the compiler choose the appropriate bit of code at compile time, instead of switching off a string at runtime:
func setValue(inout object: String) {
object = "A String"
}
func setValue(inout object: UIColor) {
object = UIColor.whiteColor()
}
func setValue(inout object: Bool) {
object = true
}
func setValue(inout object: Any) {
println("Unhandled key: \(key)")
}
This approach wouldn’t work when you have an Any and you want to indicate to the function what type is contained in the Any… but in this case, the reason you have problems is that the compiler does know what the types are, so you can take advantage of that.
-Use Any instead of AnyObject to cover value types and structs.
-You may need to cast to Any before calling your method.
Example:
func swapAny( inout obj1: Any, inout obj2: Any )
{
let temp = obj1
obj1 = obj2
obj2 = temp
}
use:
var fooString: Any = "Foo"
var barString: Any = "Bar"
swapAny(&fooString, obj2: &barString)
println(fooString)//prints "Bar"
Protocol support for inout
When you cast a class to a protocol you end up with an immutable reference, which cant be used in inout function parameters. So you can:
Use Method overloading (which can duplicate your code and make it congenitally harder to read, even if you refactor)
Not use inout (this is what you should do)
Or you can do this:
-
protocol IPositional{
func setPosition(position:CGPoint)
}
extension IPositional{
var positional:IPositional {get{return self as IPositional}set{}}
}
class A:IPositional{
var position:CGPoint = CGPoint()
func setPosition(position:CGPoint){
self.position = position
}
}
func test(inout positional:IPositional){
positional.setPosition(CGPointMake(10,10))
}
var a = A()
test(&a.positional)
a.position//output: (10.0, 10.0)
Conclusion:
The benefit of doing it this way: Is that you can now have one "inout method" for all classes that implements IPositional
But I recommend going with option 2. (Not using inout)
The type of object should match the type of parameter, including AnyObject:
func setValue(inout object: AnyObject, key: String) {
switch key {
case "String":
object = "A String"
case "UIColor":
object = UIColor.whiteColor()
case "Bool":
object = true
default:
print("Unhandled key: \(key)")
}
}
var string: AnyObject = "Default String"
var color: AnyObject = UIColor.blackColor()
var bool: AnyObject = false
setValue(&string, key: "String")
setValue(&color, key: "UIColor")
setValue(&bool, key: "Bool")

Resources