Search code examples
swiftrespondstoselector

In Swift3, combine responds#to and calling in one fell swoop?


For example,

superview?.subviews.filter{
    $0 != self &&
    $0.responds(to: #selector(setter: Blueable.blue))
  }.map{
    ($0 as! Blueable).blue = false
  }

Is there a concept like ..

x.blue??? = false

'???' meaning 'if it responds to blue, call blue'...

Note - I fully appreciate I could write an extension, callIfResponds:to, or a specific extension, blueIfBlueable.

I'm wondering if there's some native swiftyness here, which I don't know about. It seems to be a pretty basic concept.


Footnote:

in the ensuing heated discussion, there is mention of using a protocol. Just for the benefit of anyone reading, here's one approach to using a protocol:

protocol Blueable:class {
    var blue:Bool { get set }
}

extension Blueable where Self:UIView {
    func unblueAllSiblings() { // make this the only blued item
        superview?.subviews.filter{$0 != self}
            .flatMap{$0 as? Blueable}
            .forEach{$0.blue = false}
    }
}

// explanation: anything "blueable" must have a blue on/off concept.
// you get 'unblueAllSiblings' for free, which you can call from
// any blueable item to unblue all siblings (likely, if that one just became blue)

To use it, for example...

@IBDesignable
class UILabelStarred: UILabel, Blueable {

    var blueStar: UIView? = nil
    let height:CGFloat = 40
    let shinyness:CGFloat = 0.72
    let shader:Shader = Shaders.Glossy
    let s:TimeInterval = 0.35

    @IBInspectable var blue:Bool = false {
        didSet {
            if (blue == true) { unblueAllSiblings() }
            blueize()
        }
    }

    func blueize() {

        if (blueStar == nil) {
            blueStar = UIView()
            self.addSubview(blueStar!)
            ... draw, say, a blue star here
            }
        if (blue) {
            UIView.animate(withDuration: s) {
                self. blueStar!.backgroundColor = corporateBlue03
                self.textColor = corporateBlue03
            }
        }
        else {
            UIView.animate(withDuration: s) {
                self. blueStar!.backgroundColor = UIColor.white
                self.textColor = sfBlack5
            }
        }
    }
}

Just going back to the original question, that's all fine. But you can't "pick up on" an existing property (a simple example is isHidden) in existing classes.

Furthermore as long as we're discussing it, note that in that example protocol extension, you unfortunately can NOT have the protocol or extension automatically, as it were, call unblueAllSiblings from "inside" the protocol or extension, for exactly this reason: why you can't do it


Solution

  • Somehow I think what you want is:

    superview?.subviews.filter {
       $0 != self // filter out self
    }.flatMap {
      $0 as? Blueable
    }.forEach {
      $0.blue = false
    }
    

    Why should you be checking whether a class conforms to a setter when you can check the type?

    Checking selectors should not be used in pure Swift. You will need it mostly for interacting with Obj-C APIs - dynamic methods, optional methods in protocols or informal protocols.