Why Swift Enum generate object, internal variable can't be altered? - swift

I have an enum that could generate Some object, as shown below. However, when I assign the badgeValue for the Some object, it doesn't store it.
class Some {
public var badgeValue : String = "default"
}
enum MyEnum: Int {
case x
var some: Some {
let some: Some
switch self {
case .x:
some = Some()
}
return some
}
}
let x = MyEnum(rawValue: 0)
x?.some.badgeValue = "New"
print("\(x?.some.badgeValue ?? "Nothing")") // This will print `default`
I'm expecting it to print New. Why is it print default instead? If I instantiate Some directly, then the badgeValue can be updated. What's so special about the enum store var that can't be

You have made your MyEnum var some a computed property. A computed property gets evaluated every time you read its value. Thus, every time you invoke x?.some, you get a new Some object.
As Rob pointed out, my initial suggestion for a way to fix this won't work. Enums can't have stored properites. I think you'll have to use an associated value on your enum case to do what you want.

Look on you property. It's computed. Every time you create a new instance of Some class.
var some: Some {
let some: Some
switch self {
case .x:
some = Some()
}
return some
}

Related

Create an enum with parameter

I have some Outputs in my code so I regrouped all that with an Enum String.
The problem is that I have some Outputs containing variable.
Is It possible to create an Enum who takes variable ?
Exemple with this string
print("The name of the Team is \(team.name)")
I wanted to do something like that:
enum Exemple: String {
case TEAM_NAME(name: String) = "The name of the Team is \(name)"}
print(Exemple.TEAM.NAME("Team 1").rawvalue)
Thank you
It's possible to have an enum with associated values for cases. But in order to get the output you're looking for you will need a function.
enum Example {
case teamName(name: String)
case teamId(id: Int)
func printName() {
switch self {
case .teamName(name: let name):
print(name)
default:
break
}
}
}
let team = Example.teamName(name: "team1")
team.printName() // prints team1
You can define an instance method or computed property for enum that will return a string value in depend of enumeration case and associated value. See example for playground.
enum Example {
case firstItem
case secondItem(withText: String)
var stringValue: String {
switch self {
case .firstItem: return "Simple string"
case .secondItem(withText: let text): return "String with additional text: \(text)"
}
}
}
let myEnumItem: Example = .secondItem(withText: "Test")
let text = myEnumItem.stringValue
It is not possible for Enum to have both raw value and associated value. I think you can get by with the associated value. Without the raw value the enum still can provide you enough information to compose the message

Cannot assign to property: <Enum:case> is not settable

for example we have simple enum
public enum CXActionSheetToolBarButtonItem {
case cancel
case done
case now
private static var titles: [CXActionSheetToolBarButtonItem: String] = [
.cancel: "Cancel",
.done: "Done",
.now: "Now",
]
public var title: String {
get { return CXActionSheetToolBarButtonItem.titles[self] ?? String(describing: self) }
// what am I want to do
set(value) { CXActionSheetToolBarButtonItem.titles[self] = value }
}
// what am I forced to do
public static func setTitle(_ title: String, for item: CXActionSheetToolBarButtonItem) {
CXActionSheetToolBarButtonItem.titles[item] = title
}
}
why I don't can set new title like this
CXActionSheetToolBarButtonItem.cancel.title = "asd"
compiler responded error - Cannot assign to property: 'cancel' is not settable, but I can set title with function
CXActionSheetToolBarButtonItem.setTitle("asd", for: .cancel)
what should I change for worked my var as settable? .cancel.title = "asd"
Using an enum for this seems inappropriate, but I'll address the porblem at face value. You need to mark your setter as nonmutating, so that it can be called on non-var instances of your enum:
public enum CXActionSheetToolBarButtonItem {
// ...
public var title: String {
get { return CXActionSheetToolBarButtonItem.titles[self] ?? String(describing: self) }
nonmutating set(value) { CXActionSheetToolBarButtonItem.titles[self] = value }
}
}
CXActionSheetToolBarButtonItem.cancel.title = "foo" // Works... but why would you want this?!
I think the main reason why you can't do that is because the Swift compiler sees enum cases as immutable by default (similar to an immutable struct declared with let), and here you are trying to mutate it.
A good way to see this is if you try to add a mutating function to this enum
mutating public func setTitle2(_ newValue: String) {
CXActionSheetToolBarButtonItem.titles[self] = newValue
}
You will then receive the error message
error: cannot use mutating member on immutable value: 'cancel' returns immutable value
How to work around this
One way to have a similar behavior is by changing this enum into a set of static variables (which is more consistent with what you are trying to achieve).
public struct CXActionSheetToolBarButtonItem {
var title: String
static var cancel = CXActionSheetToolBarButtonItem(title: "Cancel")
static var done = CXActionSheetToolBarButtonItem(title: "Done")
static var now = CXActionSheetToolBarButtonItem(title: "Now")
}
Now you can use the following
CXActionSheetToolBarButtonItem.cancel.title = "asd"
Also, you still have the ability to use the dot-syntax
let buttonItem: CXActionSheetToolBarButtonItem = .cancel // it works
Hope that helps!
TD;DR: Because enum is an immutable object.
First of all: Whatever you're trying to do here – it's certainly not a good approach and pretty dangerous. As Craig pointed out in his comment, you're messing around with instance properties and static properties. You can have multiple instances of an enum – but when you want to change the title of a particular instance, you also change the title for all other instances. That's unexpected behavior and you should really think of another solution.
That being said, your code actually does work – with a little modification: Instead of
CXActionSheetToolBarButtonItem.cancel.title = "asd"
you can write
var item = CXActionSheetToolBarButtonItem.cancel
item.title = "asd"
This will compile.
The reason behind it is that an enum is a value type. Everytime you create an instance of an enum type, e.g. var item = CXActionSheetToolBarButtonItem.cancel, the enum's value is copied into the new variable item. You can choose if you want that value to be mutable or not by using either var or let. That's how Swift enums are intended to be used.
A single enum case like CXActionSheetToolBarButtonItem.cancel is immutable by definition.
CXActionSheetToolBarButtonItem.cancel.title = "asd"
wouldn't have any meaning because there is no instance to which the title can be assigned. The enum case .cancel is not bound to a variable.

How to reduce Swift enum conversion code

I have a variety of enums such as below.
enum PaperOrientation : Int { case portrait, landscape }
enum MetricType : Int { case inches, metric }
I made the enums of type Int, so that the values of instances could be saved as numbers with CoreData.
When retrieving the values from CoreData to use in the program, I end up with very similar conversion routines, like those shown below.
Typically, I want some default value - such as for the case where it is a new enum for the latest version of the program, and a value for that variable may not actually have been saved in CoreData. For example, the MetricType was added for the second rev of the program. Retrieving a paper created in rev 1 will not have a metric value saved. For the nil value, I want to use a default value the paper was originally assumed to have.
class ConversionRoutine {
class func orientationFor(_ num: NSNumber?) -> PaperOrientation {
if let iVal = num?.intValue {
if let val = PaperOrientation(rawValue: iVal) {
return val
}
}
return PaperOrientation(rawValue: 0)!
}
class func metricTypeFor(_ num: NSNumber?) -> MetricType {
if let iVal = num?.intValue {
if let val = MetricType(rawValue: iVal) {
return val
}
}
return MetricType(rawValue: 0)!
}
}
Is there a way to reduce the redundancy?
I present a way below that works pretty well. But welcome more refinements or improvements.
The Swift 4 example below uses a Defaultable protocol based on RawRepresentable. The first step is creating a defaultValue that can be used when the initializer fails. Note that the Defaultable protocol is not limited to Int enums. A String enum could also use it.
protocol Defaultable : RawRepresentable {
static var defaultValue : Self { get }
}
protocol IntDefaultable : Defaultable where RawValue == Int {
}
extension IntDefaultable {
static func value(for intValue : Int) -> Self {
return Self.init(rawValue: intValue) ?? Self.defaultValue
}
static func value(for num : NSNumber?) -> Self {
if let iVal = num?.intValue {
return self.value(for: iVal)
}
return Self.defaultValue
}
}
After the Defaultable protocol is defined, I can create an IntDefaultable protocol that will be used for Int enums.
In an extension to IntDefaultable, I can create the generic code to handle the conversion. First, I create a function that takes an Int. Then I create a function that takes an NSNumber optional.
Next, look at how one of the enums is built:
enum MetricType : Int, Codable, IntDefaultable { case inches, metric
static var defaultValue: MetricType = .inches
}
I also decided to declare the enum Codable, which may be useful. When I add the IntDefaultable protocol, it becomes fairly easy to add the defaultValue line of code with code completion - go to the new line and type “def”-tab, then “ = .”, and then choose one of the values from the popup. Note that often I want to pick the first enum value, but the default value could be any one.
The last thing is calling the conversion routine for getting a value from CoreData
let units = MetricType.value(for: self.metricType) // where self.metricType is the NSManagedObject member.
You can add an initializer in enum.
enum PaperOrientation : Int {
case portrait, landscape
init(number: NSNumber) {
self = PaperOrientation(rawValue: number.intValue) ?? .portrait
}
}

Is there a way to get didSet to work when changing a property in a class?

I have a class as property with a property observer. If I change something in that class, is there a way to trigger didSet as shown in the example:
class Foo {
var items = [1,2,3,4,5]
var number: Int = 0 {
didSet {
items += [number]
}
}
}
var test: Foo = Foo() {
didSet {
println("I want this to be printed after changing number in test")
}
}
test.number = 1 // Nothing happens
Nothing happens because the observer is on test, which is a Foo instance. But you changed test.number, not test itself. Foo is a class, and a class is a reference type, so its instances are mutable in place.
If you want to see the log message, set test itself to a different value (e.g. a different Foo()).
Or, add the println statement to the other didSet, the one you've already got on Foo's number property.
Or, make Foo a struct instead of a class; changing a struct property does replace the struct, because a struct is a value type, not a reference type.

Why class cannot be initialised in Switch statement in Swift

wondering why Swift switch statement does not allow to instantiate classes like in other languages and how to solve this. Would be glad if anyone can help out on this.
In the example i have created a simple Vehicle class and trying to instantiate its sub classes via switch depending on classSetter value. However the final line of print statement cannot print name property of any of the classes if it is instantiated within switch (or seems to any other kind of conditional) statement.
import UIKit
class Vehicle {
var name: String {return ""}
}
class Car: Vehicle {
override var name: String {return "Car"}
}
class Bike: Vehicle {
override var name: String {return "Bike"}
}
var classSetter:Int = 1
switch classSetter {
case 1:
println("Initializing Car")
var vehicle = Car()
case 2:
println("Initialized Bike")
let vehicle = Bike()
default:
println("just defaulted")
}
println("Name property from initialization is \(vehicle.name)")
Your two vehicles are being declared within the switch’s { }, so they only exist in that block (that is their “scope”). They don’t exist outside it, so you can’t refer to them there, hence the error.
The default solution to this (that other answers are giving) is to declare the vehicle as a var outside the switch, but here’s an alternative: wrap the switch in a closure expression and return a value from it. Why do this? Because then you can use let and not var to declare vehicle:
let vehicle: Vehicle = { // start of a closure expression that returns a Vehicle
switch classSetter {
case 1:
println("Initializing Car")
return Car()
case 2:
println("Initialized Bike")
return Bike()
default:
println("just defaulted")
return Vehicle()
}
}() // note the trailing () because you need to _call_ the expression
println("Name property from initialization is \(vehicle.name)")
It would be nice if if and switch were expressions (i.e. evaluated to a result) so you didn’t need this closure, but for now it’s a reasonable workaround.
Note, several of the answers here that use the var approach suggest making vehicle an Optional value (i.e. Vehicle?). This is not necessary – so long as the code is guaranteed to assign vehicle a value before it is used (and the compiler will check this for you), it doesn’t have to be optional. But I still think the closure expression version is a better way.
By the way, you might want to consider using a protocol for Vehicle instead of a base class, since that way you don’t have to give Vehicle a default but invalid implementation for name:
protocol Vehicle {
var name: String { get }
}
// one of the benefits of this is you could
// make Car and Bike structs if desired
struct Car: Vehicle {
var name: String {return "Car"}
}
struct Bike: Vehicle {
var name: String {return "Bike"}
}
Though this would mean you couldn’t have a default return from the switch statement of a Vehicle(). But chances are that would be bad anyway – an optional Vehicle? with nil representing failure might be a better option:
let vehicle: Vehicle? = {
switch classSetter {
case 1:
println("Initializing Car")
return Car()
case 2:
println("Initialized Bike")
return Bike()
default:
println("no valid value")
return nil
}
}()
// btw since vehicle is a Vehicle? you need to unwrap the optional somehow,
// one way is with optional chaining (will print (nil) here)
println("Name property from initialization is \(vehicle?.name)")
If you didn’t want this to be a possibility at all, you could consider making the indicator for different kinds of vehicles an enum so it could only be one of a valid set of vehicles:
enum VehicleKind {
case Bike, Car
}
let classSetter: VehicleKind = .Car
let vehicle: Vehicle = {
switch classSetter {
case .Car:
println("Initializing Car")
return Car()
case .Bike:
println("Initialized Bike")
return Bike()
// no need for a default clause now, since
// those are the only two possibilities
}
}()
Your assumption is incorrect.
The scope of the variable vehicle is inside the switch. You are then trying to access it outside the switch.
You need to create the variable outside the switch. You can still instantiate it inside.
var vehicle: Vehicle?
Switch... {
Case
vehicle = Car()
}
println(vehicle)
Writing on my phone so couldn't give full proper code but this will give you the idea.
What you're doing doesn't work in other languages either. You need to declare the vehicle variable before you enter the switch so that it's in the same scope as your println. After that, you an assign it whatever you need to inside the switch.