2

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

Community
  • 1
  • 1
Fattie
  • 27,874
  • 70
  • 431
  • 719

4 Answers4

4

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.

Hamish
  • 78,605
  • 19
  • 187
  • 280
  • You know you've hit the nail on the head here. The "super powerful for loops" in Swift is an area that many engineers - certainly me - don't know enough about or don't use enough. Good call. – Fattie Jan 28 '17 at 14:08
  • can you use the "type-casting pattern", sort of "once in one line of code for one variable" ... ? So, as I ask "x.blue??? = false" the answer would be something like ...... `(case x as Blueable)?.blue = false` ......... maybe?!? – Fattie Jan 28 '17 at 14:14
  • @JoeBlow No you cannot use it inline, only in a pattern matching context such as an `if case` or `for case`, but as others have mentioned, `(x as? Blueable)?.blue = false` is a simple way to do it inline through the use of the conditional type-casting operator `as?` and optional chaining. – Hamish Jan 28 '17 at 14:17
  • Got it - that's a shame really! Yes, understood that `(x as? Blueable)?.blue = false` is excellent. In a sense, my question was is there a "similar form" to that, specifically asking about responds. So, you can imagine something like `(x as? (.blue) )?.blue = false` or you can imagine something like `(x as? (#selector(blah)) )?.blah(42)`. Anyways I guess the answer is "no" - thanks!! – Fattie Jan 28 '17 at 14:57
  • I think a basic tension here is, as Sulth. mentions since swift is protocol-oriented, you "shouldn't use" responds#to in Swift. But the responds#to call exists. And you can trivially give good uses cases. So ... the concept "shouldn't use" is often troubled. – Fattie Jan 28 '17 at 15:00
  • @JoeBlow It's not so much that you shouldn't *ever* use `responds(to:)` in Swift, there are valid use cases for it (mainly for things like introspection, which the Obj-C runtime is great at, and Swift less so). However its use here is clunky and awkward because saying `x.responds(to: #selector(setter: Blueable.blue))` doesn't tell the compiler that `x` has a static type with the property `blue` – you have to force cast in order to achieve that (which is actually risky, because having the property `blue` doesn't automatically mean it's a `Blueable`). – Hamish Jan 28 '17 at 15:16
  • That's why using protocols and conditional type-casting is the preferred approach, as it is safe, concise and flexible. – Hamish Jan 28 '17 at 15:16
  • Hmm, understood, but what about if you're "adding one" .. so, a lot of apple's stuff responds to isHidden, and I'd make new things that (are completely conceptually unrelated, but) also respond to isHidden? Does that "feel" like responds#to to you, @hamish?? – Fattie Jan 28 '17 at 15:35
  • 1
    @JoeBlow Not really, you could create a `Hideable` protocol with an `isHidden` property requirement and then extend `UIView` to conform to it, along with any other things that you create that have an `isHidden` property :) – Hamish Jan 28 '17 at 15:37
  • Hmm - you perfectly present the "totally Swift" outlook! Good one. In any event, as I say nice one on presenting the "super powerful for loops" approach. – Fattie Jan 28 '17 at 15:42
2

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.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • Hmm, thanks x100000 for the answer; however *"Why should you be checking whether a class conforms to a setter when you can check the type?"* - how bizarre man; surely you can instantly see in the example that one could have any number of custom controls where you want to deblue? "Turning off all my siblings where I get turned on" is step one in, well, making UX you know?! – Fattie Jan 28 '17 at 14:11
  • 2
    @JoeBlow The funny thing is that you are checking if there is a setter for property `blue` and then you are force casting to `Blueable` instead of the obvious `is Blueable`. That's the whole point of the *protocol* there. – Sulthan Jan 28 '17 at 14:21
  • @JoeBlow, wouldn't `Blueable` be a protocol that you'd make all of your custom controls adopt if they have the `blue` `Bool` property? `protocol Blueable: class { var blue: Bool { get set } }` – vacawama Jan 28 '17 at 14:24
  • hey @vacawama - by all means, you could use a protocol here. Note that you still have to check (/cast, something) the protocol. – Fattie Jan 28 '17 at 14:44
  • @JoeBlow, true. My point is that the protocol would allow *any number of custom controls* to work with all of these answers. – vacawama Jan 28 '17 at 14:50
  • hey @vacawama sure, but if you check to see if it responds, you can use it with any number of custom controls. consider `isHidden`, you may be using existing apple stuff that is isHiddenAble, or your own stuff. Note that, if, as Sulthan says, "Checking selectors should not be used in Swift" and that's just an idiomatic rule about Swift - then, sure. What more can you say? "Checking selectors should not be used in Swift" and that's that. – Fattie Jan 28 '17 at 14:53
2

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

superview?.subviews.forEach {
    guard $0 !== self else { return }
    ($0 as? Blueable)?.blue = false
}
vacawama
  • 150,663
  • 30
  • 266
  • 294
Benzi
  • 2,439
  • 18
  • 25
  • just BTW wouldn't that be `continue` rather than `return` – Fattie Jan 28 '17 at 14:45
  • No, unlike a for loop (in which case it would be `continue`), `.forEach` is a function that takes in a closure that works on each iterated element. The `{...}` block after `forEach` represent a closure and not a scoped `for` block. – Benzi Jan 28 '17 at 14:48
1

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 }
Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148