0

I have a stripped down, code based test app (no Storyboard), that has:

  • UIWindow object
  • ViewController, with its main View
  • Single UIView object on top. -

To test which object is the first responder, i evaluate in tochesBegan, inside the topView as follows, but all items respond false. The Responder Chain is working because the touch is detected (both in the topView and in the main controller's view.

This is the stripped down version of the app. Given that this is not a simple view-subview hierarchy, I don't use a recursive function.

import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow()
        window?.rootViewController = ViewController()
        window?.makeKeyAndVisible()
        return true
    }
}
class ViewController: UIViewController {
    override func viewDidLoad() {
        let topView = View()
        self.view.addSubview(topView)
    }
}
class View: UIView {
    convenience init() {
        self.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        self.backgroundColor = UIColor.yellow
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let window = (UIApplication.shared).delegate?.window as? UIWindow
        let viewController = window?.rootViewController
        let mainView = viewController?.view
        print("Window?: \(window!.isFirstResponder)")
        print("View Controller?: \(viewController!.isFirstResponder)")
        print("Main View?: \(mainView!.isFirstResponder)")
        for view in (mainView?.subviews)! {
            print("Any View?: \(view.isFirstResponder)")
        }
    }
}

All evaluations return false. Am I doing something wrong here?

Regards... e

rmaddy
  • 314,917
  • 42
  • 532
  • 579
eharo2
  • 2,553
  • 1
  • 29
  • 39

2 Answers2

2

I believe without subclassing there are few view objects (UITextField, UITextView) that can become first responder, its better you check if UIView canBecomeFirstResponder.

If you want to achieve this functionality, such as highlighting, selecting of a UIView, you have to subclass the UIView and override the becomeFirstResponder, as following:

class CustomView: UIView {

    override var isFirstResponder: Bool {
        return true
    }

}

To test above, add following in your ViewController:

override func loadView() {
    self.view = CustomView()
}

Now check to see if our CustomView isFirstResponder:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    print("View: \(self.view.isFirstResponder)")
}
AamirR
  • 11,672
  • 4
  • 59
  • 73
  • Thx @AamirR, I learned something new. Using UITextView instead of UIView works. That not all views can be firstResponder is a bit contradictory. See: Understanding Event Handling, Responders, and the Responder Chain - For every type of event, UIKit designates a first responder and sends the event to that object first. The first responder varies based on the type of event. Touch events The first responder is the view in which the touch occurred https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/understanding_event_handling_responders_and_the_responder_chain – eharo2 Feb 23 '18 at 19:06
  • You see the example of `class CustomView: UIView`, out of the box (without subclassing) only few view objects (UITextField, UITextView) can become first responder, you can still make a UIView or a UILabel to be first responder, you just have to subclass the view and override the properties. not really contradictory, is it? – AamirR Feb 24 '18 at 07:26
  • Without subclassing, there is no way a base UIView class (or a UILabel etc...) can tell you that its the first responder, you are welcome, and thanks for this post too )) – AamirR Feb 24 '18 at 07:34
2

I think there is no first responder from start. You can still receive events to your views.

Responder object
Mouse events and multitouch events first go to the view that is under the mouse pointer or finger; that view might or might not be the first responder.

More on responders

nextResponder
The UIResponder class does not store or set the next responder automatically, so this method returns nil by default.

More on nextResponder

LGP
  • 4,135
  • 1
  • 22
  • 34
  • Thank you @LGP, you are right. It seems that the documentation is contradictory... https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/understanding_event_handling_responders_and_the_responder_chain - Understanding Event Handling, Responders, and the Responder Chain - Determining the First Responder for an Event For every type of event, UIKit designates a first responder and sends the event to that object first. The first responder varies based on the type of event. Touch events The first responder is the view in which the touch occurred. – eharo2 Feb 23 '18 at 18:57
  • Yes, interestingly that link says `There is no single responder chain within your app. UIKit defines default rules for how objects are passed from one responder to the next, but you can always change those rules by overriding the appropriate properties in your responder objects.` The chain seems to be more of a traversal algorithm, with just a pointer to the current `firstResponder` - if any. Moreover, the firstResponder seems only be used for keyboard events, since clicks go to the view below the click. – LGP Feb 23 '18 at 19:05