3

I was trying to create an extension function on the UIColor which could take a parameter of type Card.Colour and return a UIColor back to the caller.

button.backgroundColor = UIColor.getColour(cardColour: cardToDeal.colour)


extension UIColor {
    func getColour(cardColour: Card.Colour) -> UIColor {
        switch cardColour {
        case .Red:
            return UIColor.red
        case .Green:
            return UIColor.green
        case .Blue:
            return UIColor.blue
        }
    }
}

When I tried to do this, the extension function of UIColor.getColour required me to enter a parameter of type UIColor rather than the specified type of Card.Colour in the extension method.

However, when I changed the extension function of getColour to either:

static func getColour(cardColour: Card.Colour) -> UIColor {
class func getColour(cardColour: Card.Colour) -> UIColor {

It allowed me to pass a parameter of type Card.Colour

Why is this? Why does changing the function to either a static function or a class function change the type required to be passed in?

Thanks in advance!

(A detailed answer would be much appreciated)

Alex Marchant
  • 570
  • 6
  • 21
  • If you create a static or a class method you will be extending the type UIColor and you won’t have access to its instance (`self`) . If you create a “regular” method you can access its UIColor instance value and that’s why they are called instance methods. – Leo Dabus Jan 29 '18 at 22:02

2 Answers2

8

Remember that UIColor is a class. A class is kind of like a blueprint that you use to create instances or objects that conform to that class. UIColor.red is an example of an instance of the UIColor class.

When you define a func inside a class (in your case, as an extension), Swift assumes that you want to add that func to the blueprint, which will in turn be available in all instances of the UIColor class, like UIColor.red.

You could also define your func outside of all classes, by just placing it on the top level of the module, instead of inside an extension.

However, to keep your functions organized, you can place functions like that inside the class name. You just have to tell Swift that you're not trying to add the function to the blueprint that will be applied to all instances, and that all you want instead is to have a function whose name is prefixed with the name of the class.

Here's an example to illustrate the difference in usage:

class Test {
    func notStatic() {
        print("called from an instance")
    }

    static func thisIsStatic() {
        print("called on class name directly")
    }
}

let instance = Test() // this is an *instance* of Test

instance.notStatic() // we can call a non static func on instance

Test.thisIsStatic() // we can call a static func directly on the class only

Now, let's go back to your specific example for a second. Notice that in your example, you're starting with a instance of Card.Colour and trying to create a new instance of UIColor.

In other words, adding a func to UIColor instances (i.e., a non-static or class) is useless to you, because you don't have an instance of UIColor yet.

The idiomatic way of creating a new instance of a class is using an initializer (init). So you could turn your function into an initializer on UIColor like this:

extension UIColor {
    convenience init(cardColour: Card.Colour) {
        switch cardColour {
        case .Red: self.init(cgColor: UIColor.red.cgColor)
        case .Blue: self.init(cgColor: UIColor.blue.cgColor)
        case .Green: self.init(cgColor: UIColor.green.cgColor)
        }
    }
}

Now you just call UIColor(cardColour: .Red) to get what you want. Note that in the implementation, I'm converting UIColor.red to cgColor and back as a quick hack. Feel free to use the initializer you see fit on UIColor for each case of Card.Colour.

But there's another way, which I think is even more elegant. Since you already have an instance of Card.Colour, you can extend Card.Colour with a function that gives you the UIColor corresponding to the instance. Within that function, you can refer to the Card.Colour instance using the keyword self.

Since you already have the Card.Colour instance through self, you don't need to pass any arguments to that function. This allows you to use a cool feature called computed properties to make the usage even nicer.

This is how you'd add such an extension to Card.Colour:

extension Card.Colour {
    var uiColor: UIColor {
        switch self {
        case .Red: return .red
        case .Blue: return .blue
        case .Green: return .green
        }
    }
}

And you can then get a UIColor from a Card.Colour like this Card.Colour.Red.uiColor or mainColour.uiColor, where mainColour is of type Card.Colour.

Finally, as Leo Dabus noted in a comment, Swift's naming conventions is that cases should start with a lowercase letter. You should be using Card.Colour.red instead of Card.Colour.Red, etc. Those conventions came out around Swift 3 time. It was common to capitalize case names before then.

hashemi
  • 2,608
  • 1
  • 25
  • 31
  • 1
    `self = .whatever` it is wrong !!! enumeration cases are immutable. uiColor is a computed property. you are supposed to return `return .whateverColor`. `self =` would only be possible in a convenience initializer of UIColor. Note that is is Swift convention to names all your cases starting with a lowercase letter. – Leo Dabus Jan 29 '18 at 23:54
  • @LeoDabus Thanks Leo. I focused too much on organization and sloppily copied and pasted implementation details from OP's code without sufficient modification. I have now fixed that. Any suggestions on further improving `UIColor.init(cardColour:)`. I agree enum cases should start with lowercase letters but I kept them as per the OP's code to avoid confusion. – hashemi Jan 30 '18 at 04:51
  • @hashemi Thanks! Just one clarification, when the extension method was just a normal func, I needed to pass an instance of UIColor in... So what is the point in having the parameter of cardColour of type Card.Colour if I am not able to pass that type into the function, shouldn't that cause an error of some sort? – Alex Marchant Jan 30 '18 at 09:59
  • 1
    If the extension was on `UIColor` then you wouldn’t pass a UIColor instance as an argument. What you do instead is create an instance before you can call that func. After you create the instance, you’d call the `func` on that instance. Swift will “magically” make that instance available inside the func through the `self` keyword. The func allows you to manipulate or get data from the instance of that class. But you don’t want to manipulate a `UIColor`, you want to create a new one. – hashemi Jan 30 '18 at 13:18
2

An extension method operates on an instance of provided type. You can use all of the internal properties and methods of an instance inside the method block.

static methods are methods that are namespaced by the class name and do not operate on any specific instance of your class. class methods are pretty much the same, just the difference between class and static is, that you can override class methods in a subclass.

bartlomiej.n
  • 500
  • 6
  • 19