How to create enum that takes parameter? - ios

This is my enum
enum urlLink: String {
case linkOne = "http://test.com/linkOne"
}
and it works well in most of my cases. However, I now want to create another link which will takes parameters and it looks like this
"http://test.com/:params/info"
is there a way that I can add string parameter to one of my enum cases so that I can have something like this for linkTwo
enum urlLink: String {
case linkOne = "http://test.com/linkOne"
case linkTwo(input: String) = "http://test.com/" + input + "info"
}
Thank you so much!

You can't add raw values to enums with associated values. You can, however add properties and methods to your enum, so you can do something like this:
enum urlLink: CustomStringConvertible {
case linkOne
case linkTwo(input: String)
var description: String {
switch self {
case .linkOne:
return "http://test.com/linkOne"
case .linkTwo(let input):
return "http://test.com/\(input)info"
}
}
}
You can also conform to RawRepresentable:
enum urlLink: RawRepresentable {
case linkOne
case linkTwo(input: String)
var rawValue: String {
switch self {
case .linkOne:
return "http://test.com/linkOne"
case .linkTwo(let input):
return "http://test.com/\(input)info"
}
}
init?(rawValue: String) {
if rawValue == "http://test.com/linkOne" {
self = .linkOne
return
} else {
self = // some regex logic to get the "input" part
return
}
return nil
}
typealias RawValue = String
}

Related

How to pass enum value inside GraphQL query

GraphsQL mutation gives the following error. I have attached my Query and code.
GraphQLResult<Data>(data: nil, errors: Optional([Validation error of type UnknownType: Unknown type TicketType, Validation error of type FieldUndefined: Field 'addTicket' in type 'Mutation' is undefined # 'addTicket']), extensions: nil, source: Apollo.GraphQLResult<MyProject.MyMutation.Data>.Source.server, dependentKeys: nil)
Query:
mutation MyMutation($id: String!, $ticketType: TicketType) {
addTicket(input: { id: $id, ticketType: $ticketType}) {
accountId
storyId
}
}
And Inside API.swift this Enum gets generated automatically from the schema.json file.
public enum TicketType: RawRepresentable, Equatable, Hashable, CaseIterable, Apollo.JSONDecodable, Apollo.JSONEncodable {
public typealias RawValue = String
case normal
case firstClass
case secondClass
/// Auto generated constant for unknown enum values
case __unknown(RawValue)
public init?(rawValue: RawValue) {
switch rawValue {
case "NORMAL": self = .normal
case "FIRST_CLASS": self = .firstClass
case "SECOND_CLASS": self = .secondClass
default: self = .__unknown(rawValue)
}
}
public var rawValue: RawValue {
switch self {
case .normal: return "NORMAL"
case .firstClass: return "FIRST_CLASS"
case .secondClass: return "SECOND_CLASS"
case .__unknown(let value): return value
}
}
public static func == (lhs: TicketType, rhs: TicketType) -> Bool {
switch (lhs, rhs) {
case (.normal, .normal): return true
case (.firstClass, .firstClass): return true
case (.secondClass, .secondClass): return true
case (.__unknown(let lhsValue), .__unknown(let rhsValue)): return lhsValue == rhsValue
default: return false
}
}
public static var allCases: [TicketType] {
return [
.normal,
.firstClass,
.secondClass,
]
}
}
And in my code, I am calling this method as follows
myNetworkObj.apollo.perform(mutation: addTicket(id: "1234", ticketType: .normal) {
result in ....
}
I don't believe your immediate problem is with enums, but rather how you are calling the mutation.
The mutation's argument signature needs to match the schema when you call it in the client:
mutation: addTicket(input{id: "1234", ticketType: .normal})
addTicket's top-level arg is input in your schema.

Why the swift compiler cannot use my subscript?

I have code that resembles this
I created custom subscript to do the unwrapping for me to make things easier.
enum MyEnum {
case one
case two
case three
}
struct MyStruct {
static var empty: Self {
return .init()
}
//Some Variables Here
}
class MyClass {
var index = 0
var data: [MyEnum: MyStruct] = [
.two: .empty,
.three: .empty,
.one: .empty
]
var allTypes: [MyEnum] {
switch Bool.random() {
case true:
return [.two, .three]
case false:
return [.one]
}
}
var currentSelectedType: MyEnum {
return allTypes[index]
}
func myMethod(type: MyEnum) {
let one: MyStruct = data[type]!
let two: MyStruct = data[currentSelectedType]!
let three: MyStruct = data[allTypes[index]]
let four: MyStruct = data[.one]
}
}
fileprivate extension Dictionary {
subscript(_ key: Key) -> Value where Key == MyEnum, Value == MyStruct {
get {
return self[key]!
}
set {
self[key] = newValue
}
}
}
in my MyClass myMethod why I have to forcely unwrapp one and two but not three and four otherwise I get a compile time error
let one: MyStruct = data[type] // Error Value of optional type 'MyStruct?' must be unwrapped to a value of type 'MyStruct'
let two: MyStruct = data[currentSelectedType] //Error Value of optional type 'MyStruct?' must be unwrapped to a value of type 'MyStruct'
Is there something I'm missing here?
I don't have an answer on why compiler isn't picking the expected overload in this situation.
I would recommend clarifying the overload you wish to use at call site, like following.
fileprivate extension Dictionary {
subscript(safe key: Key, defaultValue: Value = .empty) -> Value where Key == MyEnum, Value == MyStruct {
get { return self[key, default: defaultValue] }
set { self[key] = newValue }
}
}
With above, you can tell compiler explicitly to use your preferred overload.
func myMethod(type: MyEnum) {
let one: MyStruct = data[safe: type]
let two: MyStruct = data[safe: currentSelectedType]
let three: MyStruct = data[safe: allTypes[index]]
let four: MyStruct = data[safe: .one]
}

Can I keep array of string as RawValue of case in enum (Swift)?

Currently, I want to keep array as a rawValue in enum base but they show error like this
Raw type '[String]' is not expressible by a string, integer, or floating-point literal
Can someone suggest me about this?
MyEnum that I need.
enum PaymentState: [String] {
case pending = ["first", "second"]
}
One way to achieve that kind of style, you can use RawRepresentable
enum PaymentState: RawRepresentable {
typealias RawValue = Array<String>
init?(rawValue: Array<String>) {
if rawValue == ["first", "second"] {
self = .pending
}
return nil
}
var rawValue: Array<String> {
switch self {
case .pending:
return ["first", "second"]
}
}
case pending
}
PaymentState.pending.rawValue // ["first", "second"]
The alternate solution you have is
enum PaymentState{
case pending
var list: [String] {
switch self {
case .pending:
return ["first", "second"]
}
}
}
You can access like this
PaymentState.pending.list
The advantage of this solution is when you will add more cases. In enum, you can add that in switch case add assess value through the list or you can give any name.

Enum with associated value does not conform to CaseIterable and throws error

The following enum works fine without any error.
enum EnumOptions: CaseIterable {
case none
case mild
case moderate
case severe
case unmeasurable
}
When I try to add an associated value to one of the cases, it throws the following error "Type 'EnumOptions' does not conform to protocol 'CaseIterable'. Do you want to add protocol stubs?"
enum OedemaOptions: CaseIterable {
case none
case mild
case moderate
case severe
case unmeasurable(Int)
}
After adding stubs,
enum OedemaOptions: CaseIterable {
typealias AllCases = <#type#>
case none
case mild
case moderate
case severe
case unmeasurable(Int)
What should be filled up in the placeholder to make the Enum conform to CaseIterable, since there is only 1 case with associated value and not all the cases?
Automatic synthesis does not work for enums with associated values. You need to provide a custom implementation of the allCases property. Try,
enum OedemaOptions: CaseIterable {
static var allCases: [OedemaOptions] {
return [.none, .mild, .moderate, .severe, .unmeasurable(-1)]
}
case none
case mild
case moderate
case severe
case unmeasurable(Int)
}
You forgot to account for all 18,446,744,073,709,551,616 Ints.
Also, each one is an Option, not an Options.
static var allCases: [OedemaOption] {
[.none, .mild, .moderate, .severe]
+ (.min...(.max)).map(unmeasurable)
}
Compiler doesn't support automatic synthesis for CaseIterable in case with associated value(s).
This is my alternative solution to CaseIterable
import Foundation
protocol CustomCaseIterable {
associatedtype AllCustomCases: Hashable
static var allCustomCases: [AllCustomCases: Self] { get }
}
extension CustomCaseIterable {
init(_ string: String?, defaultCase: Self) {
self = Self(string) ?? defaultCase
}
init?(_ string: String?) {
guard
let string = string,
let caseValue = Self.allCustomCases.first(where: { "\($0.0)" == string })?.value
else {
return nil
}
self = caseValue
}
}
Example
enum MyEnum {
case one(Bool)
case two(Int)
case three(String)
case four
}
extension MyEnum: CustomCaseIterable {
static var allCustomCases: [String : Self] = [
"one_true": .one(true),
"two_zero": .two(.zero),
"three_empty": .three(""),
"four": .four
]
}
for (key, _) in MyEnum.allCustomCases {
print(key)
}
Extending that concept to RawRepresentable
extension CustomCaseIterable where AllCustomCases: RawRepresentable {
init(_ string: String?, defaultCase: Self) {
self = Self(string) ?? defaultCase
}
init?(_ string: String?) {
guard
let string = string,
let caseValue = Self.allCustomCases.first(where: { "\($0.0.rawValue)" == string })?.value
else {
return nil
}
self = caseValue
}
}
Example
enum MapStyle {
case primary(border: Bool)
case secondary(border: Bool)
case placeholder
}
enum JSONMapStyle: String, CaseIterable {
case primary
case primaryWithBorder = "PRIMARY_WITH_BORDER"
case secondary
case secondaryWithBorder = "SECONDARY_WITH_BORDER"
case placeholder
}
extension MapStyle: CustomCaseIterable {
static var allCustomCases: [JSONMapStyle: MapStyle] = [
.primary: .primary(border: false),
.primaryWithBorder: .primary(border: true),
.secondary: .secondary(border: false),
.secondaryWithBorder: .secondary(border: true),
.placeholder: .placeholder
]
}
for (key, _) in MapStyle.allCustomCases {
print(key.rawValue)
}

Protocol conforming to Equatable for Diffing

I have a small chat app here.
I can have 2 types of messages:
- text
- video
I am using polymorphism while decoding the JSON like so:
import Foundation
enum MessageType: Int, Decodable {
case text
case video
}
protocol Message: Decodable {
static var type: MessageType { get }
var id: String { get }
var user: User { get}
var timestamp: String { get }
}
struct TextMessage: Message {
static var type: MessageType = .text
var id: String
var user: User
var timestamp: String
let text: String
}
struct VideoMessage: Message {
static var type: MessageType = .video
var id: String
var user: User
var timestamp: String
let text: String
let link: String
let poster: String
}
enum MessageWrapper: Decodable {
enum CodingKeys: String, CodingKey {
case type
}
case text(TextMessage)
case video(VideoMessage)
var item: Message {
switch self {
case .text(let item): return item
case .video(let item): return item
}
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let type = try values.decode(Int.self, forKey: .type)
switch type {
case MessageType.text.rawValue: self = .text(try TextMessage(from: decoder))
case MessageType.video.rawValue: self = .video(try VideoMessage(from: decoder))
default:
throw DecodingError.dataCorruptedError(forKey: .type,
in: values,
debugDescription: "Invalid type")
}
}
}
I am also using the MVVM approach like so:
struct ChatViewModel {
enum ViewModelType {
case loading
case text(TextMessageViewModel)
case video(VideoMessageViewModel)
case failure(ErrorViewModel)
}
enum State {
case initialized
case loading
case loaded([Message])
case failed(Error)
}
let state: State
let viewModels: [ViewModelType]
init(with state: State) {
self.state = state
switch state {
case .initialized:
viewModels = []
case .loading:
viewModels = [
.loading,
]
......
}
}
In order to be able to use a Diffing library like Differ, the ChatViewModel should conform to the Equatable protocol.
extension ChatViewModel: Equatable {
static func == (lhs: ChatViewModel, rhs: ChatViewModel) -> Bool {
return lhs.state == rhs.state
}
}
extension ChatViewModel.State: Equatable {
static func == (lhs: ChatViewModel.State, rhs: ChatViewModel.State) -> Bool {
switch (lhs, rhs) {
case (.initialized, .initialized): return true
case (.loading, .loading): return true
case let (.loaded(l), .loaded(r)): return l == r
case let (.failed(l), .failed(r)): return l.localizedDescription == r.localizedDescription
default: return false
}
}
}
The problem here is for the case let (.loaded(l), .loaded(r)): return l == r, Message, as a protocol, doesn't conform to Equatable.
Making it conform to Equatable like
protocol Message: Decodable, Equatable {
static var type: MessageType { get }
var id: String { get }
var user: User { get}
var timestamp: String { get }
}
produce an error Protocol 'Message' can only be used as a generic constraint because it has Self or associated type requirements for the MessageWrapper:
enum MessageWrapper: Decodable {
...
var item: Message { // Protocol 'Message' can only be used as a generic constraint because it has Self or associated type requirements
switch self {
case .text(let item): return item
case .video(let item): return item
}
}
...
}
Any idea or suggestion to have a clean way to solve this? I saw some post about Type Erasure but after some tests I am not sure that it is actually solving the problem.
You don't have to conform to Equatable in order to be able to use the == operator. You can just define an operator like that yourself, without conforming to Equatable.
For convenience's sake, I'll assume that TextMessage and VideoMessage already conforms to Equatable.
First, write a method that compares Messages:
func messageEqual(m1: Message, m2: Message) -> Bool {
if let textMessage1 = m1 as? TextMessage, let textMessage2 = m2 as? TextMessage {
return textMessage1 == textMessage2
}
if let videoMessage1 = m1 as? VideoMessage, let videoMessage2 = m2 as? VideoMessage {
return videoMessage1 == videoMessage2
}
return false
}
Then a the == operator for [Message]:
func ==(messages1: [Message], messages2: [Message]) -> Bool {
return messages1.count == messages2.count &&
zip(messages1, messages2).allSatisfy(messageEqual)
}
Now l == r should compile.

Resources