1

Simple scenario: I have a base ViewController with a method that I would like to optionally override in subclasses. The twist, is that I would like to do the override in an extension of the subclassed ViewController.

This does not work:

class BaseViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        overrideStyles()
    }

    func overrideStyles() {
        // do nothing here. Optionally overridden as needed
    }
}

class ViewController: BaseViewController {
}

// this is in another file that is to a specific target
extension ViewController {
    override func overrideStyles() {
        print("YAY!")
    }
}

Neither does it work with protocols:

protocol OverridesStyles {
    func overrideStyles()
}

extension OverridesStyles {
    func overrideStyles() {
        print("default method that does nothing")
        // do nothing, this makes the method optional
    }
}

class BaseViewController: UIViewController, OverridesStyles {
    override func viewDidLoad() {
        super.viewDidLoad()

        overrideStyles()
    }
}

// this is in another file that is to a specific target
extension ViewController {
    func overrideStyles() {
        print("YAY!")
    }
}

I can't subclass the entire ViewController class because that's the class in the Storyboard and I can't have a different Storyboard per target.

Is there any other way to do this? The idea was that I could have different targets and a different extension per target because the overrideStyles method may reference different UI elements depending on the target.

Thanks.

Aaron Bratcher
  • 6,051
  • 2
  • 39
  • 70

4 Answers4

2

The documentation gives the answer, it's a rule:

Extensions can add new functionality to a type, but they cannot override existing functionality.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • The idea was that I could have different targets and a different extension per target because the overrideStyles method may reference different UI elements depending on the target. – Aaron Bratcher Apr 01 '16 at 14:09
  • Why don't just?: class ViewController: BaseViewController { override func overrideStyles() { super.overrideStyles() //if you want to print("YAY!") } } It should work. – glm4 Apr 01 '16 at 14:11
  • @SherM4n No, I can't do that. Because the ViewController class must be used in the Storyboard. I can't have a different Storyboard per target. – Aaron Bratcher Apr 01 '16 at 14:17
  • @AaronBratcher I don't see why storyboards came to the table, but, basically if you viewcontroller inherits from baseviewcontroller, you can override its methods and do inside whatever you want. And also have this two view controllers in differents storyboards or the same, depends on your needs. – glm4 Apr 01 '16 at 14:26
  • I have a ViewController class that is instantiated through the Storyboard. If I want to override it in different ways, I cannot subclass because if I subclass, then the Storyboard reference needs to specify the new class. – Aaron Bratcher Apr 01 '16 at 14:30
0

I cannot explain why, but overriding a method in a subclass extension seems to work only if the to-be-overridden method in the base class is also defined in an extension.

Here is a self-contained example:

class A : NSObject {
    override init() {
        super.init()
        foo()
        bar()
    }

    func foo() {
        print("foo from A called")
    }
}

extension A {
    func bar() {
        print("bar from A called")
    }
}

class B : A { }

extension B {
    override func foo() {
        print("foo from B called")
    }
    override func bar() {
        print("bar from B called")
    }
}

let b = B() // foo from A called, bar from B called

As one can see, the overridden method bar() from the subclass B is called as expected, but the foo() method from the base class A.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
0

Okay... I found an interesting answer to this problem that accomplishes what I want. Applying the OverridesStyles protocol to the BaseViewController would not allow me to do a protocol extension to the ViewController class. (Well it would allow it, but it didn't work)

However, Applying the OverridesStyles protocol to the ViewController class would let me do a protocol extension successfully. So it seems, you cannot do a protocol extension to a subclass but only to the same class.

So, in summary, this works:

protocol OverridesStyles {
    func overrideStyles()
}

extension OverridesStyles {
    func overrideStyles() {
        print("default method that does nothing")
        // do nothing, this makes the method optional
    }
}

class ViewController: BaseViewController, OverridesStyles {
    override func viewDidLoad() {
        super.viewDidLoad()

        overrideStyles()
    }
}

// this is in another file that is to a specific target
extension ViewController {
    func overrideStyles() {
        print("YAY!")
    }
}
Aaron Bratcher
  • 6,051
  • 2
  • 39
  • 70
0

I had the same issue, if I used the prototype option it complained that the function was not present in the main class, yet if I added it to the main class as the base version it complained with 'invalid redeclaration of ...' as it thinks 2 declarations of the same function.

Try this:

// Main class file
class TestViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        overrideStyles()
    }
}

// In a separate file with Target set to one Target
extension TestViewController {
    func overrideStyles() {
        print("default method that does nothing")
        // do nothing, this makes the method optional
    }
}

// In another separate file with Target set to another Target
extension TestViewController {
    func overrideStyles() {
        print("YAY!")
    }
}

I found it complains nicely when building for a Target if an extension for that Target is not defined and you don't get the prompt in the main View Controller to add the function which would break it. Also this would mean you can use the TestViewController just fine in a single storyboard which targets any Target you wish. So you now have 1 storyboard with 1 bulk code as the base and a function with variation only. In my working version this was used to change the action on a IBAction and it works well.

@IBAction func continueTapped() {
    continueAction()
}