Fattie
Fattie

Reputation: 12590

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

Upvotes: 2

Views: 826

Answers (4)

Benzi
Benzi

Reputation: 2459

Why not check conformance to the type directly? Something like:

superview?.subviews.forEach {
    guard $0 !== self else { return }
    ($0 as? Blueable)?.blue = false
}

Upvotes: 2

Hamish
Hamish

Reputation: 80791

As others have said, a better way of going about this is by conditionally type-casting rather than using responds(to:). However, don't overlook just using a for in loop – they're pretty powerful, allowing you to use pattern matching and where clauses, allowing you to iterate over a given subset of elements.

for case let blueable as Blueable in superview?.subviews ?? [] where blueable !== self {
    blueable.blue = false
}
  • case let blueable as Blueable uses the type-casting pattern in order to only match elements that are Blueable, and bind them to blueable if successful.

  • where blueable !== self excludes self from being matched.

Upvotes: 4

Luca Angeletti
Luca Angeletti

Reputation: 59496

You can add this extension

extension UIView {
    var siblings: [UIView] { return self.superview?.subviews.filter { $0 != self } ?? [] }
}

Now pick the solution that you prefer among the followings

Solution 1

siblings.flatMap { $0 as? Blueable }.forEach { $0.blue = false  }

Solution 2

siblings.forEach { ($0 as? Blueable)?.blue = false }

Upvotes: 1

Sulthan
Sulthan

Reputation: 130102

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.

Upvotes: 2

Related Questions