Home > Blockchain >  Build error: Must call a designated initializer of the superclass 'UIControl'
Build error: Must call a designated initializer of the superclass 'UIControl'

Time:09-27

I want to use one initializer or the other depending on the iOS version. If it is not iOS 14, I want to set the action to use later. My call to the super constructor happens inside an if/else:

class Control: UIControl {

    var action: (() -> Void)?

    init(action: @escaping () -> Void) {
        self.action = action
        if #available(iOS 14.0, *) {
            let primaryAction = UIAction(handler: { _ in action() })
            super.init(frame: .zero, primaryAction: primaryAction)
        } else {
            super.init(frame: .zero)
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}

The call is:

let controlThatDoesNotWork = Control(action: { print("this doesn not work") })

The build error is

error: must call a designated initializer of the superclass 'UIControl'
    super.init(frame: .zero, primaryAction: primaryAction)
    ^

Any idea how I need to call this convenience initializer so it builds?

CodePudding user response:

Your init(action: @escaping () -> Void) is a designated initializer, and designated initializers are not allowed to call convenience initializers from the base class, they must call another designater initializer.

This is enforced here:

Rule 1
    A designated initializer must call a designated initializer from its immediate superclass.

This the base initializer in discussion:

/// Initializes the control and adds primaryAction for the UIControlEventPrimaryActionTriggered control event. Subclasses of UIControl may alter or add behaviors around the usage of primaryAction, see subclass documentation of this initializer for additional information.
@available(iOS 14.0, *)
public convenience init(frame: CGRect, primaryAction: UIAction?)

So, you need to convert your initializer to a convenience one, and have to call with self instead of super the other intializers (again, due to the initializer rules):

class Control: UIControl {

    var action: (() -> Void)?

    convenience init(action: @escaping () -> Void) {
        if #available(iOS 14.0, *) {
            let primaryAction = UIAction(handler: { _ in action() })
            self.init(frame: .zero, primaryAction: primaryAction)
        } else {
            self.init(frame: .zero)
        }
        self.action = action
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    } 
}

Going forward, as the sole reason for the action property to exist is to support iOS 13, which doesn't have the init(frame:primaryAction:) initializer, you can write the code that adds the primary action, and get rid of the property:

class Control: UIControl {
    
    convenience init(action: @escaping () -> Void) {
        let primaryAction = UIAction(handler: { _ in action() })
        if #available(iOS 14.0, *) {
            self.init(frame: .zero, primaryAction: primaryAction)
        } else {
            self.init(frame: .zero)
            addAction(primaryAction, for: .primaryActionTriggered)
        }
    }
  • Related