0

I used to use this code to get the corresponding UIViewController of my UIView:

func parentVC(forView view: UIView) -> UIViewController? {
    var responder = view as UIResponder
    while (responder != nil) {
        if let viewController = responder as? UIViewController {
            return viewController
        }
        responder = responder.next!
    }
    return nil
}

However, I got a warning from the compiler:

Comparing non-optional value of type 'UIResponder' to 'nil' always returns true

So I tried moving responder = responder.next! inside the while loop condition:

func parentVC(forView view: UIView) -> UIViewController? {
    var responder = view as UIResponder
    while (responder = responder.next!) {
        if let viewController = responder as? UIViewController {
            return viewController
        }
    }
    return nil
}

Now the compiler is giving me an error:

Use of '=' in a boolean context, did you mean '=='?

No, I didn't mean to use the double equals sign. I understand that this message may help to catch typos, but why can't Xcode make this error message into a warning message, and allow me to compile and run my code? What is preventing this code from working?

Xcoder
  • 1,433
  • 3
  • 17
  • 37
  • if your `responder.next` has some optional value and you would like to unwrap that optional to use in `while` loop what you need is `let responder = responder.next` instead of `(responder = responder.next!)`. It is not clear what you are trying above – lionserdar Feb 27 '20 at 03:38
  • @lionserdar Good point. This removed the first warning about optionals that I had received from the compiler, but not the second. – Xcoder Feb 27 '20 at 03:40
  • `Use of '=' in a boolean context, did you mean '=='?` one of the answers below explains that issue. In your while loop you should have some conditional flag, like bool (true or false) but what you are doing is an assignment. You can't do that in while loop condition check!! – lionserdar Feb 27 '20 at 03:45
  • @lionserdar Why can't I do that in my while loop? Just because it may cause an infinite loop doesn't mean it shouldn't compile. – Xcoder Feb 27 '20 at 03:45
  • Please read the `while loop` section in `swift control flow` doc. https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html – lionserdar Feb 27 '20 at 03:47
  • It seems that all of the previous responses are completely overlooking the OP real question. Xcoder is perfectly aware of the meaning of `=` vs `==`. They are asking **why** Swift chooses not to allow assignment within expressions. – Brian61354270 Feb 27 '20 at 03:50
  • 1
    @Brian but that's what I answered before. That's how the language is. You want me to find, like, minutes of some meeting where they decided this? What? – matt Feb 27 '20 at 03:57
  • 1
    You already know _why_ (proven by your last paragraph). It is a safety feature, because C history is full of bugs where people wrote `if (foo = bar)` instead of `if (foo == bar)`. And people notoriously don't pay attention to warnings. Swift requires you to be explicit about your intentions. – Amadan Feb 27 '20 at 04:10
  • Thank you all for helping me through this question, I appreciate the thoughtful explanations. – Xcoder Feb 27 '20 at 04:44

3 Answers3

6

The reason for the error with

while (responder = responder.next!) {

is that the only thing permitted after the keyword while is a condition, and the only thing that a condition can be is an expression that evaluates to a Bool. An assignment cannot be a Bool.


What you're trying to do is perfectly reasonable; I do it all the time. Here's my formulation:

    var r : UIResponder! = view
    repeat { r = r.next } while !(r is UIViewController)
    let vc = r as! UIViewController

You'll notice I don't check for nil at all in that formulation; if we reach the end of the responder chain without finding a view controller, something is very wrong and I want to crash.

Another formulation I use just walks the whole chain and prints it; in that one, I do check for nil, obviously:

    var r : UIResponder! = someResponder
    repeat { print(r as Any, "\n"); r = r.next } while r != nil
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 3
    One aspect that may be worth highlighting is that this was a choice made by the language designers. Assignments could have been treated as expressions, as is the case in some languages that the OP may be familiar with. However, Swift chooses not to implement this feature, either for the sake of simplifying the compiler, improving code readability, or some other reason relevant to the language. – Brian61354270 Feb 27 '20 at 04:00
  • @Brian I think I _did_ highlight that. Even if an assignment had a value, this assignment’s value would not be a Bool, so it still wouldn’t work as a condition. It is particularly characteristic of Swift that only a Bool is a Bool: zero and nil are not Bools and cannot be converted to Bools. – matt Feb 27 '20 at 19:31
1

The assignment operator (=) doesn’t return a value, to prevent it from being mistakenly used when the equal to operator (==) is intended.

(from https://docs.swift.org/swift-book/LanguageGuide/BasicOperators.html)

I think that makes == pointless in Swift, because, without having to disambiguate, = could do everything. I assume both are in the language because of historical familiarity.

Recommendation:

  sequence(first: self, next: \.next)
    .lazy
    .compactMap { $0 as? UIViewController }
    .first

Or

extension Sequence {
  func getFirst<T>() -> T? {
    lazy.compactMap { $0 as? T } .first
  }
}

+

extension UIResponder {
  var parentVC: UIViewController? {
    next.flatMap { parent in
      sequence(first: parent, next: \.next).getFirst()
    }
  }
}
0

The program won't compile because "responder = responder.next!" is an assignment, not a boolean comparison. your initial program receives a warning because it generates an infinite while-loop.

Max Miller
  • 45
  • 5
  • Thanks for the quick answer, but don't assignments in `if` and `while` statements [work in other languages](https://stackoverflow.com/questions/151850/why-would-you-use-an-assignment-in-a-condition)? – Xcoder Feb 27 '20 at 03:38
  • Also, my while loop should eventually return a value, so it wouldn't be infinite. – Xcoder Feb 27 '20 at 03:39
  • 2
    Because you are writing Swift, and not other languages. – Amadan Feb 27 '20 at 03:44
  • @Amadan Why does Swift give me this error and not other languages? – Xcoder Feb 27 '20 at 03:46
  • your while loop has an expression that will always return true since UIResponder will never be nil, thus the construct will never terminate, regardless of its ability to return the correct value. – Max Miller Feb 27 '20 at 03:48
  • @MaxMiller If my code hits the return statement, wouldn't it cause the while loop to exit? – Xcoder Feb 27 '20 at 03:51
  • @Xcoder This is correct, I'm explaining what the initial warning meant, in the context of your problem the loop would terminate when a value is returned. – Max Miller Feb 27 '20 at 03:54