How can I force focus to change to a specific view in tvOS? - objective-c

I am implementing custom code to handle a click on the Menu button on the Siri Remote.
How can I force focus to change to my custom menu when pressing the menu button?

For ios 10 you should use preferredFocusEnvironments instead of preferredFocusedView .
In below example if you want to focus on button then see below code.
#IBOutlet weak var button: UIButton!
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [button]
}
override func viewDidLoad() {
super.viewDidLoad()
setNeedsFocusUpdate()
updateFocusIfNeeded()
}

Finally figured it out myself. You have to override the preferredFocusedView property of your UIView or UIViewController.
In Swift it works like this:
func myClickHandler() {
someCondition = true
self.setNeedsFocusUpdate()
self.updateFocusIfNeeded()
someCondition = false
}
override weak var preferredFocusedView: UIView? {
if someCondition {
return theViewYouWant
} else {
return defaultView
}
}
I can't quite remember how to override getters in Objective-C so if someone want to post that I'll edit the answer.

Here is another implementation based on Slayters answer above. Its slightly more elegant I think than using the conditional booleans.
Put this in your viewcontroller
var viewToFocus: UIView? = nil {
didSet {
if viewToFocus != nil {
self.setNeedsFocusUpdate();
self.updateFocusIfNeeded();
}
}
}
override weak var preferredFocusedView: UIView? {
if viewToFocus != nil {
return viewToFocus;
} else {
return super.preferredFocusedView;
}
}
Then to use it in your code
viewToFocus = myUIView;

here is the objective C
- (UIView *)preferredFocusedView
{
if (someCondition) {
// this is if your menu is a tableview
NSIndexPath *ip = [NSIndexPath indexPathForRow:2 inSection:0];
UITableViewCell * cell = [self.categoryTableView cellForRowAtIndexPath:ip];
return cell;
}
return self.view.preferredFocusedView;
}
in your viewDidLoad or view did appear do something like this:
UIFocusGuide *focusGuide = [[UIFocusGuide alloc]init];
focusGuide.preferredFocusedView = [self preferredFocusedView];
[self.view addLayoutGuide:focusGuide];
if you want to do it when it first launches

Here's a nice little Swift 2 copy / paste snippet:
var myPreferredFocusedView: UIView?
override var preferredFocusedView: UIView? {
return myPreferredFocusedView
}
func updateFocus(to view: UIView) {
myPreferredFocusedView = napDoneView
setNeedsFocusUpdate()
updateFocusIfNeeded()
}
Use it like this:
updateFocus(to: someAwesomeView)

#elu5ion 's answer, but in objective-c
first declare:
#property (nonatomic) UIView *preferredView;
Set these methods:
-(void)setPreferredView:(UIView *)preferredView{
if (preferredView != nil) {
_preferredView = nil;
UIFocusGuide *focusGuide = [[UIFocusGuide alloc]init];
[self.view addLayoutGuide:focusGuide];
focusGuide.preferredFocusedView = [self preferredFocusedView];
[self setNeedsFocusUpdate];
[self updateFocusIfNeeded];
}
_preferredView = preferredView;
}
- (UIView *)preferredFocusedView {
if (_preferredView) {
return _preferredView;
}
return self.view.preferredFocusedView;
}

Related

Handle long press on a single UITabBarItem

I use the long press gesture on a tab bar. But I only need the long press gesture for one particular tab bar item.
How can I solve this problem? Could I customize the long press gesture in tab bar?
Here's how I did it using Swift 3:
protocol MyTabControllerProtocol: class {
func tabLongPressed()
}
class MyTabController: UITabBarController {
func viewDidLoad() {
super.viewDidLoad()
viewControllers = [
// add your view controllers for each tab bar item
// NOTE: if you want view controller to respond to long press, then it should extend MyTabControllerProtocol
]
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(astroButtonItemLongPressed(_:)))
tabBar.addGestureRecognizer(longPressRecognizer)
}
func astroButtonItemLongPressed(_ recognizer: UILongPressGestureRecognizer) {
guard recognizer.state == .began else { return }
guard let tabBar = recognizer.view as? UITabBar else { return }
guard let tabBarItems = tabBar.items else { return }
guard let viewControllers = viewControllers else { return }
guard tabBarItems.count == viewControllers.count else { return }
let loc = recognizer.location(in: tabBar)
for (index, item) in tabBarItems.enumerated() {
guard let view = item.value(forKey: "view") as? UIView else { continue }
guard view.frame.contains(loc) else { continue }
if let nc = viewControllers[index] as? UINavigationController {
if let vc = nc.viewControllers.first as? MyTabControllerProtocol {
vc.tabLongPressed()
}
} else if let vc = viewControllers[index] as? MyTabControllerProtocol {
vc.tabLongPressed()
}
break
}
}
}
You can subclass UITabBarController and add a UILongPressGestureRecognizer to it's tabBar. Acting as the delegate of the gesture recognizer will allow you to be selective over when it will detect a long press. Since the tab bar item will be selected as soon as the user touches it you can use the selectedItem property to perform this check.
#interface TabBarController () <UIGestureRecognizerDelegate>
#property (nonatomic, strong) UILongPressGestureRecognizer *longPressRecognizer;
#end
#implementation TabBarController
- (void)viewDidLoad {
[super viewDidLoad];
self.longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(recognizerFired:)];
self.longPressRecognizer.delegate = self;
[self.tabBar addGestureRecognizer:self.longPressRecognizer];
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
// This will ensure the long press only occurs for the
// tab bar item which has it's tag set to 1.
// You can set this in Interface Builder or in code
// wherever you are creating your tabs.
if (self.tabBar.selectedItem.tag == 1) {
return YES;
}
else {
return NO;
}
}
- (void)recognizerFired:(UILongPressGestureRecognizer *)recognizer {
// Handle the long press...
}
#end
Here is a solution in swift 5 :
Add longpress Gesture recognizer to the "Entire" tabbar using storyboard or code..
and Don't forget to let your ViewController be its delegate .. and implement the delegate method below
to check if the incoming touch is inside "one" of your tabbar subViews .. if yes return true ,, else return false ..
here are the code that will let the recognizer fire only when we longPress on the first tab:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view?.isDescendant(of: tabBar.subviews[1]) == true {return true}
return false
}
Note: tabbar.subviews array count is number of the items + 1 which is the background of the tabbar .. so if you want the view of the first item you can fint it and index 1 not 0
I did this by getting the specific tabBarItem's view that user can interact and simply added the long press gesture to that. With that way you do not have to write any protocols or subclass the TabBarViewController.
let longPressGestureRecognizer = UILongPressGestureRecognizer.init(target: self, action: #selector(longTap(_:)))
longPressGestureRecognizer.minimumPressDuration = 1.0
self.tabBarController?.orderedTabBarItemViews()[0].addGestureRecognizer(longPressGestureRecognizer)
And as for getting the tabBarItemViews :
extension UITabBarController {
func orderedTabBarItemViews() -> [UIView] {
let interactionViews = tabBar.subviews.filter({$0.isUserInteractionEnabled})
return interactionViews.sorted(by: {$0.frame.minX < $1.frame.minX})
}
P.S. : The viewController, namely "self" is the first item for the tabBarController.
If you just need to recognize a long press on one of the tabBar items, you can do this in the corresponding viewController's viewDidLoad method:
UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget: self action: #selector(handleLongPress:)];
[self.tabBarController.tabBar addGestureRecognizer: longPressGesture];
And then:
- (void)handleLongPress:(UILongPressGestureRecognizer *) recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
UITabBar *tabBar = ((UITabBar* )recognizer.view);
if (tabBar.selectedItem == self.tabBarItem) {
doSomethingVeryExciting();
}
}
}
This won't fire if you just switch tabs.

Current view controller from AppDelegate?

Is there a way to get the current view controller from the AppDelegate? I know there is rootViewController, but that's not what I'm looking for.
If your app's root view controller is a UINavigationController you can do this:
((UINavigationController*)appDelegate.window.rootViewController).visibleViewController;
Similarly, if it's a UITabBarController you can do this:
((UITabBarController*)appDelegate.window.rootViewController).selectedViewController;
Of course, explicit casting like this is dirty. Better would be to capture the reference yourself using strong types.
This might help
- (UIViewController *)topViewController{
return [self topViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
- (UIViewController *)topViewController:(UIViewController *)rootViewController
{
if (rootViewController.presentedViewController == nil) {
return rootViewController;
}
if ([rootViewController.presentedViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navigationController = (UINavigationController *)rootViewController.presentedViewController;
UIViewController *lastViewController = [[navigationController viewControllers] lastObject];
return [self topViewController:lastViewController];
}
UIViewController *presentedViewController = (UIViewController *)rootViewController.presentedViewController;
return [self topViewController:presentedViewController];
}
Swift version:
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
Taken from:
https://gist.github.com/snikch/3661188
If you have UINavigationController into appDelegate then use its property topViewController or visibleViewController
Make an extension:
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let tab = base as? UITabBarController {
let moreNavigationController = tab.moreNavigationController
if let top = moreNavigationController.topViewController where top.view.window != nil {
return topViewController(top)
} else if let selected = tab.selectedViewController {
return topViewController(selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(presented)
}
return base
}
}
Usage:
if let rootViewController = UIApplication.topViewController() {
//do sth with root view controller
}
Get the appDelegate object:
MyAppDelegate *tmpDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
As beryllium suggested you can use the UINavigationController's properties to access your current view controller.
So the code would look like:
id myCurrentController = tmpDelegate.myNavigationController.topViewController;
or:
NSArray *myCurrentViewControllers = tmpDelegate.myNavigationController.viewControllers;
You can get the current view controller from rootViewController by looking for its presentedViewController, like this:
UIViewController *parentViewController = [[[UIApplication sharedApplication] delegate] window].rootViewController;
while (parentViewController.presentedViewController != nil){
parentViewController = parentViewController.presentedViewController;
}
UIViewController *currentViewController = parentViewController;
It works with me. Hope it helps :)
For anyone not using a UINavigationControllerbut rather their default view controller is a UIViewController you can check which view controller is active (or presented) with the following in AppDelegate:
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> Int {
if let rootViewController = self.window!.rootViewController {
if let presentedViewController = rootViewController.presentedViewController {
return presentedViewController.supportedInterfaceOrientations()
}
} // Else current view controller is DefaultViewController
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
}
As you can see I'm checking for the current view controller in order to support different interface orientations for specific view controllers. For anyone else interested in using this method to support specific the following should be placed in each view controller that needs a specific orientation.
override func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientationMask.All.rawValue)
}
Note: This code was written with Swift 1.2
UIApplication extension in Swift 4+ syntax based on A.G's solution
public extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
let moreNavigationController = tab.moreNavigationController
if let top = moreNavigationController.topViewController, top.view.window != nil {
return topViewController(base: top)
} else if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
Sample usage:
if let rootViewController = UIApplication.topViewController() {
//do something with rootViewController
}
Swift Solution:
self.window.rootViewController.presentedViewController.
That should get you what you need.
Often I need to retrieve the view controller that is currently displayed. It could mean the view controller at the top of the stack of the current UINavigationController, the currently presented view controller, etc. So I wrote this function which figures it out most of the time, and that you can use inside a UIViewController extension.
Code in Swift 3:
func currentViewController(
_ viewController: UIViewController? =
UIApplication.shared.keyWindow?.rootViewController)
-> UIViewController? {
guard let viewController =
viewController else { return nil }
if let viewController =
viewController as? UINavigationController {
if let viewController =
viewController.visibleViewController {
return currentViewController(viewController)
} else {
return currentViewController(
viewController.topViewController)
}
} else if let viewController =
viewController as? UITabBarController {
if let viewControllers =
viewController.viewControllers,
viewControllers.count > 5,
viewController.selectedIndex >= 4 {
return currentViewController(
viewController.moreNavigationController)
} else {
return currentViewController(
viewController.selectedViewController)
}
} else if let viewController =
viewController.presentedViewController {
return viewController
} else if viewController.childViewControllers.count > 0 {
return viewController.childViewControllers[0]
} else {
return viewController
}
}
Call it with: currentViewController()
If anyone wants in Objective C.
GlobalManager.h
//
// GlobalManager.h
// Communicator
//
// Created by Mushrankhan Pathan on 21/10/21.
// Copyright © 2021 Ribbideo. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface GlobalManager : NSObject
typedef void (^ ActionBlock)(void);
+(UIViewController*)currentController;
+(UIViewController*)currentController:(UIViewController*) baseController;
#end
NS_ASSUME_NONNULL_END
GlobalManager.m
//
// GlobalManager.m
// Communicator
//
// Created by Mushrankhan Pathan on 21/10/21.
// Copyright © 2021 Ribbideo. All rights reserved.
//
#import "GlobalManager.h"
#implementation GlobalManager
+(UIViewController*)currentController
{
UIViewController *base = UIApplication.sharedApplication.keyWindow.rootViewController;
return [GlobalManager currentController:base];
}
+(UIViewController*)currentController:(UIViewController*) baseController
{
if ([baseController isKindOfClass:[UINavigationController class]]) {
return [GlobalManager currentController:((UINavigationController*)baseController).visibleViewController];
}
if ([baseController isKindOfClass:[UITabBarController class]]) {
UINavigationController* moreNavigationController = ((UITabBarController*)baseController).moreNavigationController;
UIViewController* top = moreNavigationController.topViewController;
if (top.view.window != nil) {
return [GlobalManager currentController:top];
}
UIViewController* selectedViewController = ((UITabBarController*)baseController).selectedViewController;
if (selectedViewController != nil) {
return [GlobalManager currentController:selectedViewController];
}
}
if (baseController.presentedViewController != nil) {
return [GlobalManager currentController:baseController.presentedViewController];
}
return baseController;
}
#end
How to use.
UIViewController *currentVC = [GlobalManager currentController];

Customize NSToolbar - Disable "Use small size"

How do I disable the "Use small size" option in the toolbar? I am using Xcode 4.
(That's the option that appears when users go to customize the Toolbar.)
If you're not distributing on the Mac App Store, and don't mind subclassing private methods, you can create an NSToolbarSubclass and override _allowsSizeMode: to return NO:
- (BOOL)_allowsSizeMode:(NSToolbarSizeMode)mode {
return mode != NSToolbarSizeModeSmall;
}
This has the added benefit of removing the checkbox from the customization sheet, as well.
You could subclass NSToolbar, override -setSizeMode: and in your implementation call [super setSizeMode: NSToolbarSizeModeRegular];.
If you're instantiating the toolbar in Interface Builder then make sure you assign your subclass to the toolbar in the nib.
#implementation RKToolbar
- (void)setSizeMode:(NSToolbarSizeMode)aSizeMode
{
[super setSizeMode:NSToolbarSizeModeRegular];
}
#end
This won't remove the checkbox from the customize panel but it will prevent it from doing anything.
There's not really a supported way to remove the checkbox. This does work but it's pretty hacky:
//in your NSToolbar subclass
- (void)runCustomizationPalette:(id)sender
{
[super runCustomizationPalette:sender];
NSWindow* toolbarWindow = [NSApp mainWindow];
NSWindow* sheet = [toolbarWindow attachedSheet];
for(NSView* view in [[sheet contentView] subviews])
{
if([view isKindOfClass:[NSButton class]])
{
if([[[(NSButton*)view cell] valueForKey:#"buttonType"] integerValue] == NSSwitchButton)
{
[view setHidden:YES];
}
}
}
}
Thanks to Rob Keniger for the excellent start. If you can have your custom toolbar as a delegate of your window, you can avoid having "Use small size" visible by getting at the sheet before it is displayed on screen. Do this by implementing [NSToolbar window:willPositionSheet:usingRect:] in the custom toolbar class. Elsewhere in your code, you'll need to do:
[myWindowWithToolbar setDelegate:myInstanceOfXXToolbar];
Here's the updated custom toolbar class:
#implementation XXToolbar
- (void)setSizeMode:(NSToolbarSizeMode)aSizeMode
{
[super setSizeMode:NSToolbarSizeModeRegular];
}
- (NSRect)window:(NSWindow *)window willPositionSheet:(NSWindow *)sheet usingRect:(NSRect)rect {
NSView *buttonView = nil;
for(NSView* view in [[sheet contentView] subviews])
{
if([view isKindOfClass:[NSButton class]])
{
if([[[(NSButton*)view cell] valueForKey:#"buttonType"] integerValue] == NSSwitchButton)
{
buttonView = view;
break;
}
}
}
if (buttonView) {
[buttonView setHidden:YES];
// This is important as it causes the sheet to redraw without the button off screen
[[sheet contentView] display];
}
return rect;
}
#end
Hope you find this useful.
Here's a Swift 2.2 version of #MacGreg's solution. You can keep your NSWindowDelegate wherever you like, just ensure at least the following is called:
var toolbar: UniformToolbar!
func window(window: NSWindow, willPositionSheet sheet: NSWindow, usingRect rect: NSRect) -> NSRect {
toolbar.removeSizeToggle(window: sheet)
return rect
}
Toolbar Subclass without the Checkbox
class UniformToolbar: NSToolbar {
override var sizeMode: NSToolbarSizeMode {
get {
return NSToolbarSizeMode.Regular
}
set { /* no op */ }
}
func removeSizeToggle(window window: NSWindow) {
guard let views = window.contentView?.subviews else { return }
let toggle: NSButton? = views.lazy
.flatMap({ (view: NSView) -> NSButton? in view as? NSButton })
.filter({ (button: NSButton) -> Bool in
guard let buttonTypeValue = button.cell?.valueForKey("buttonType")?.unsignedIntegerValue,
buttonType = NSButtonType(rawValue: buttonTypeValue)
else { return false }
return buttonType == .SwitchButton
})
.first
toggle?.hidden = true
window.contentView?.display()
}
}

How can I easily save the Window size and position state using Obj-C?

What is the best way to remember the Windows position between application loads using Obj-C? I am using Interface Builder for the interface, is it possible to do this with bindings.
What is the recommended method? Thank you.
Put a name that is unique to that window (e.g. "MainWindow" or "PrefsWindow") in the Autosave field under Attributes in Interface Builder. It will then have its location saved in your User Defaults automatically.
To set the Autosave name programmatically, use -setFrameAutosaveName:. You may want to do this if you have a document-based App or some other situation where it doesn't make sense to set the Autosave name in IB.
Link to documentation.
In Swift:
class MainWindowController : NSWindowController {
override func windowDidLoad() {
shouldCascadeWindows = false
window?.setFrameAutosaveName("MainWindow")
super.windowDidLoad()
}
According to the doc, to save a window's position:
NSWindow *window = // the window in question
[[window windowController] setShouldCascadeWindows:NO]; // Tell the controller to not cascade its windows.
[window setFrameAutosaveName:[window representedFilename]]; // Specify the autosave name for the window.
I tried all the solutions. It can only saves the position, not the size. So we should do that manually. This is how I do it on my GifCapture app https://github.com/onmyway133/GifCapture
class MainWindowController: NSWindowController, NSWindowDelegate {
let key = "GifCaptureFrameKey"
override func windowDidLoad() {
super.windowDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(windowWillClose(_:)), name: Notification.Name.NSWindowWillClose, object: nil)
}
override func awakeFromNib() {
super.awakeFromNib()
guard let data = UserDefaults.standard.data(forKey: key),
let frame = NSKeyedUnarchiver.unarchiveObject(with: data) as? NSRect else {
return
}
window?.setFrame(frame, display: true)
}
func windowWillClose(_ notification: Notification) {
guard let frame = window?.frame else {
return
}
let data = NSKeyedArchiver.archivedData(withRootObject: frame)
UserDefaults.standard.set(data, forKey: key)
}
}
In Swift 5.2, in your NSWindowController class:
override func windowDidLoad() {
super.windowDidLoad()
self.windowFrameAutosaveName = "SomeWindowName"
}
That's all there is to it!
Based on onmyway133's answer I wrote a RestorableWindowController class. As long as your window controller inherits from it, position and size for your windows are restored.
import Cocoa
open class RestorableWindowController: NSWindowController {
// MARK: - Public -
open override func windowDidLoad() {
super.windowDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(windowWillClose), name: NSWindow.willCloseNotification, object: nil)
if let frame = storedFrame {
window?.setFrame(frame, display: true)
}
}
open override func awakeFromNib() {
super.awakeFromNib()
if let frame = storedFrame {
window?.setFrame(frame, display: true)
}
}
open override var contentViewController: NSViewController? {
didSet {
if let frame = storedFrame {
window?.setFrame(frame, display: true)
}
}
}
// MARK: - Private -
private var storedFrameKey: String {
String(describing: type(of: self)) + "/storedFrameKey"
}
private var storedFrame: NSRect? {
guard let string = UserDefaults.standard.string(forKey: storedFrameKey) else {
return nil
}
return NSRectFromString(string)
}
#objc private func windowWillClose() {
guard let frame = window?.frame else {
return
}
UserDefaults.standard.set(NSStringFromRect(frame), forKey: storedFrameKey)
}
}
For me, the following line in -applicationDidFinishLaunching in the app delegate workes fine (under Catalina, macOS 10.15):
[self.window setFrameAutosaveName: #"NameOfMyApp"];
it is important that this line
[self.window setDelegate: self];
is executed before setFrameAutosaveName in -applicationDidFinishLaunching !
In order to restore a window, you can set the Restoration ID in Interface Builder. This will be used as part of the key under which the frame is stored in NSUserDefaults. -- but that didn't (always) work for me.
NSWindow has setFrameUsingName(_:) etc. to configure this, like #BadmintonCat wrote, and you can serialize the window position manually, too, in case that doesn't work, either.
The simplest solution in my app though was to use the NSWindowController.windowFrameAutosaveName property and set it to something in awakeFromNib(_:). That single line affected loading and saving successfully.
Got sick and tired of Apples AutoSave and IB BS which sometimes does and sometimes doesn't work and depends on flag settings in System Prefs blah blah blah. Just do this, and it ALWAYS WORKS and even remembers users full screen state!
-(void)applicationDidFinishLaunching:(NSNotification *)notification
{
[_window makeKeyAndOrderFront:self];
// Because Saving App Position and Size is FUBAR
NSString *savedAppFrame = [userSettings stringForKey:AppScreenSizeAndPosition];
NSRect frame;
if(savedAppFrame) {
frame = NSRectFromString(savedAppFrame);
[_window setFrame:frame display:YES];
}
else
[_window center];
// Because saving of app size and position on screen doesn't remember full screen
if([userSettings boolForKey:AppIsFullScreen])
[_window toggleFullScreen:self];
}
-(void)windowDidEnterFullScreen:(NSNotification *)notification
{
[userSettings setBool:YES forKey:AppIsFullScreen];
}
-(BOOL)windowShouldClose:(NSWindow *)sender
{
// Have to use this to set zoom state because exit full screen state always called on close
if(sender == _window) {
[userSettings setBool:(_window.isZoomed ? YES:NO) forKey:AppIsFullScreen];
}
return YES;
}
-(void)applicationWillTerminate:(NSNotification *)aNotification
{
[userSettings setObject:NSStringFromRect(_window.frame) forKey:AppScreenSizeAndPosition];
[userSettings synchronize];
}
like everyone else I found that setting it programmatically works...
self.windowFrameAutosaveName = NSWindow.FrameAutosaveName("MyWindow")
but ONLY if you do NOT also set it in IB!
If you set it in both... you're back to not working.
BTW: I found this out by literally adding "WTF" to the end of the one in code, and suddenly having everything working! 😬

Get the current first responder without using a private API

I submitted my app a little over a week ago and got the dreaded rejection email today. It tells me that my app cannot be accepted because I'm using a non-public API; specifically, it says,
The non-public API that is included in your application is firstResponder.
Now, the offending API call is actually a solution I found here on SO:
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *firstResponder = [keyWindow performSelector:#selector(firstResponder)];
How do I get the current first responder on the screen? I'm looking for a way that won't get my app rejected.
If your ultimate aim is just to resign the first responder, this should work: [self.view endEditing:YES]
In one of my applications I often want the first responder to resign if the user taps on the background. For this purpose I wrote a category on UIView, which I call on the UIWindow.
The following is based on that and should return the first responder.
#implementation UIView (FindFirstResponder)
- (id)findFirstResponder
{
if (self.isFirstResponder) {
return self;
}
for (UIView *subView in self.subviews) {
id responder = [subView findFirstResponder];
if (responder) return responder;
}
return nil;
}
#end
iOS 7+
- (id)findFirstResponder
{
if (self.isFirstResponder) {
return self;
}
for (UIView *subView in self.view.subviews) {
if ([subView isFirstResponder]) {
return subView;
}
}
return nil;
}
Swift:
extension UIView {
var firstResponder: UIView? {
guard !isFirstResponder else { return self }
for subview in subviews {
if let firstResponder = subview.firstResponder {
return firstResponder
}
}
return nil
}
}
Usage example in Swift:
if let firstResponder = view.window?.firstResponder {
// do something with `firstResponder`
}
A common way of manipulating the first responder is to use nil targeted actions. This is a way of sending an arbitrary message to the responder chain (starting with the first responder), and continuing down the chain until someone responds to the message (has implemented a method matching the selector).
For the case of dismissing the keyboard, this is the most effective way that will work no matter which window or view is first responder:
[[UIApplication sharedApplication] sendAction:#selector(resignFirstResponder) to:nil from:nil forEvent:nil];
This should be more effective than even [self.view.window endEditing:YES].
(Thanks to BigZaphod for reminding me of the concept)
Here's a category that allows you to quickly find the first responder by calling [UIResponder currentFirstResponder]. Just add the following two files to your project:
UIResponder+FirstResponder.h:
#import <Cocoa/Cocoa.h>
#interface UIResponder (FirstResponder)
+(id)currentFirstResponder;
#end
UIResponder+FirstResponder.m:
#import "UIResponder+FirstResponder.h"
static __weak id currentFirstResponder;
#implementation UIResponder (FirstResponder)
+(id)currentFirstResponder {
currentFirstResponder = nil;
[[UIApplication sharedApplication] sendAction:#selector(findFirstResponder:) to:nil from:nil forEvent:nil];
return currentFirstResponder;
}
-(void)findFirstResponder:(id)sender {
currentFirstResponder = self;
}
#end
The trick here is that sending an action to nil sends it to the first responder.
(I originally published this answer here: https://stackoverflow.com/a/14135456/322427)
Here is a Extension implemented in Swift based on Jakob Egger's most excellent answer:
import UIKit
extension UIResponder {
// Swift 1.2 finally supports static vars!. If you use 1.1 see:
// http://stackoverflow.com/a/24924535/385979
private weak static var _currentFirstResponder: UIResponder? = nil
public class func currentFirstResponder() -> UIResponder? {
UIResponder._currentFirstResponder = nil
UIApplication.sharedApplication().sendAction("findFirstResponder:", to: nil, from: nil, forEvent: nil)
return UIResponder._currentFirstResponder
}
internal func findFirstResponder(sender: AnyObject) {
UIResponder._currentFirstResponder = self
}
}
Swift 4
import UIKit
extension UIResponder {
private weak static var _currentFirstResponder: UIResponder? = nil
public static var current: UIResponder? {
UIResponder._currentFirstResponder = nil
UIApplication.shared.sendAction(#selector(findFirstResponder(sender:)), to: nil, from: nil, for: nil)
return UIResponder._currentFirstResponder
}
#objc internal func findFirstResponder(sender: AnyObject) {
UIResponder._currentFirstResponder = self
}
}
It's not pretty, but the way I resign the firstResponder when I don't know what that the responder is:
Create an UITextField, either in IB or programmatically. Make it Hidden. Link it up to your code if you made it in IB.
Then, when you want to dismiss the keyboard, you switch the responder to the invisible text field, and immediately resign it:
[self.invisibleField becomeFirstResponder];
[self.invisibleField resignFirstResponder];
For a Swift 3 & 4 version of nevyn's answer:
UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
Here's a solution which reports the correct first responder (many other solutions won't report a UIViewController as the first responder, for example), doesn't require looping over the view hierarchy, and doesn't use private APIs.
It leverages Apple's method sendAction:to:from:forEvent:, which already knows how to access the first responder.
We just need to tweak it in 2 ways:
Extend UIResponder so it can execute our own code on the first responder.
Subclass UIEvent in order to return the first responder.
Here is the code:
#interface ABCFirstResponderEvent : UIEvent
#property (nonatomic, strong) UIResponder *firstResponder;
#end
#implementation ABCFirstResponderEvent
#end
#implementation UIResponder (ABCFirstResponder)
- (void)abc_findFirstResponder:(id)sender event:(ABCFirstResponderEvent *)event {
event.firstResponder = self;
}
#end
#implementation ViewController
+ (UIResponder *)firstResponder {
ABCFirstResponderEvent *event = [ABCFirstResponderEvent new];
[[UIApplication sharedApplication] sendAction:#selector(abc_findFirstResponder:event:) to:nil from:nil forEvent:event];
return event.firstResponder;
}
#end
Using Swift and with a specific UIView object this might help:
func findFirstResponder(inView view: UIView) -> UIView? {
for subView in view.subviews as! [UIView] {
if subView.isFirstResponder() {
return subView
}
if let recursiveSubView = self.findFirstResponder(inView: subView) {
return recursiveSubView
}
}
return nil
}
Just place it in your UIViewController and use it like this:
let firstResponder = self.findFirstResponder(inView: self.view)
Take note that the result is an Optional value so it will be nil in case no firstResponder was found in the given views subview hierarchy.
The first responder can be any instance of the class UIResponder, so there are other classes that might be the first responder despite the UIViews. For example UIViewController might also be the first responder.
In this gist you will find a recursive way to get the first responder by looping through the hierarchy of controllers starting from the rootViewController of the application's windows.
You can retrieve then the first responder by doing
- (void)foo
{
// Get the first responder
id firstResponder = [UIResponder firstResponder];
// Do whatever you want
[firstResponder resignFirstResponder];
}
However, if the first responder is not a subclass of UIView or UIViewController, this approach will fail.
To fix this problem we can do a different approach by creating a category on UIResponder and perform some magic swizzeling to be able to build an array of all living instances of this class. Then, to get the first responder we can simple iterate and ask each object if -isFirstResponder.
This approach can be found implemented in this other gist.
Hope it helps.
Iterate over the views that could be the first responder and use - (BOOL)isFirstResponder to determine if they currently are.
Rather than iterate through the collection of views looking for the one that has isFirstResponder set, I too send a message to nil, but I store the receiver of the message so I can return it and do whatever I wish with it.
Additionally, I zero out the optional that holds the found responder in a defer statement from within the call itself. This ensures no references remain--even weak ones--at the end of the call.
import UIKit
private var _foundFirstResponder: UIResponder? = nil
extension UIResponder {
static var first:UIResponder? {
// Sending an action to 'nil' implicitly sends it to the first responder
// where we simply capture it and place it in the _foundFirstResponder variable.
// As such, the variable will contain the current first responder (if any) immediately after this line executes
UIApplication.shared.sendAction(#selector(UIResponder.storeFirstResponder(_:)), to: nil, from: nil, for: nil)
// The following 'defer' statement runs *after* this getter returns,
// thus releasing any strong reference held by the variable immediately thereafter
defer {
_foundFirstResponder = nil
}
// Return the found first-responder (if any) back to the caller
return _foundFirstResponder
}
// Make sure to mark this with '#objc' since it has to be reachable as a selector for `sendAction`
#objc func storeFirstResponder(_ sender: AnyObject) {
// Capture the recipient of this message (self), which is the first responder
_foundFirstResponder = self
}
}
With the above, I can resign the first responder by simply doing this...
UIResponder.first?.resignFirstResponder()
But since my API actually hands back whatever the first responder is, I can do whatever I want with it.
Here's an example that checks if the current first responder is a UITextField with a helpMessage property set, and if so, shows it in a help bubble right next to the control. We call this from a 'Quick Help' button on our screen.
func showQuickHelp(){
if let textField = UIResponder?.first as? UITextField,
let helpMessage = textField.helpMessage {
textField.showHelpBubble(with:helpMessage)
}
}
The support for the above is defined in an extension on UITextField like so...
extension UITextField {
var helpMessage:String? { ... }
func showHelpBubble(with message:String) { ... }
}
Now to support this feature, all we have to do is decide which text fields have help messages and the UI takes care of the rest for us.
Peter Steinberger just tweeted about the private notification UIWindowFirstResponderDidChangeNotification, which you can observe if you want to watch the firstResponder change.
If you just need to kill the keyboard when the user taps on a background area why not add a gesture recognizer and use it to send the [[self view] endEditing:YES] message?
you can add the Tap gesture recogniser in the xib or storyboard file and connect it to an action,
looks something like this then finished
- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
[[self view] endEditing:YES];
}
Just it case here is Swift version of awesome Jakob Egger's approach:
import UIKit
private weak var currentFirstResponder: UIResponder?
extension UIResponder {
static func firstResponder() -> UIResponder? {
currentFirstResponder = nil
UIApplication.sharedApplication().sendAction(#selector(self.findFirstResponder(_:)), to: nil, from: nil, forEvent: nil)
return currentFirstResponder
}
func findFirstResponder(sender: AnyObject) {
currentFirstResponder = self
}
}
This is what I did to find what UITextField is the firstResponder when the user clicks Save/Cancel in a ModalViewController:
NSArray *subviews = [self.tableView subviews];
for (id cell in subviews )
{
if ([cell isKindOfClass:[UITableViewCell class]])
{
UITableViewCell *aCell = cell;
NSArray *cellContentViews = [[aCell contentView] subviews];
for (id textField in cellContentViews)
{
if ([textField isKindOfClass:[UITextField class]])
{
UITextField *theTextField = textField;
if ([theTextField isFirstResponder]) {
[theTextField resignFirstResponder];
}
}
}
}
}
This is what I have in my UIViewController Category. Useful for many things, including getting first responder. Blocks are great!
- (UIView*) enumerateAllSubviewsOf: (UIView*) aView UsingBlock: (BOOL (^)( UIView* aView )) aBlock {
for ( UIView* aSubView in aView.subviews ) {
if( aBlock( aSubView )) {
return aSubView;
} else if( ! [ aSubView isKindOfClass: [ UIControl class ]] ){
UIView* result = [ self enumerateAllSubviewsOf: aSubView UsingBlock: aBlock ];
if( result != nil ) {
return result;
}
}
}
return nil;
}
- (UIView*) enumerateAllSubviewsUsingBlock: (BOOL (^)( UIView* aView )) aBlock {
return [ self enumerateAllSubviewsOf: self.view UsingBlock: aBlock ];
}
- (UIView*) findFirstResponder {
return [ self enumerateAllSubviewsUsingBlock:^BOOL(UIView *aView) {
if( [ aView isFirstResponder ] ) {
return YES;
}
return NO;
}];
}
With a category on UIResponder, it is possible to legally ask the UIApplication object to tell you who the first responder is.
See this:
Is there any way of asking an iOS view which of its children has first responder status?
You can choose the following UIView extension to get it (credit by Daniel):
extension UIView {
var firstResponder: UIView? {
guard !isFirstResponder else { return self }
return subviews.first(where: {$0.firstResponder != nil })
}
}
You can try also like this:
- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event {
for (id textField in self.view.subviews) {
if ([textField isKindOfClass:[UITextField class]] && [textField isFirstResponder]) {
[textField resignFirstResponder];
}
}
}
I didn't try it but it seems a good solution
This is good candidate for recursion! No need to add a category to UIView.
Usage (from your view controller):
UIView *firstResponder = [self findFirstResponder:[self view]];
Code:
// This is a recursive function
- (UIView *)findFirstResponder:(UIView *)view {
if ([view isFirstResponder]) return view; // Base case
for (UIView *subView in [view subviews]) {
if ([self findFirstResponder:subView]) return subView; // Recursion
}
return nil;
}
you can call privite api like this ,apple ignore:
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
SEL sel = NSSelectorFromString(#"firstResponder");
UIView *firstResponder = [keyWindow performSelector:sel];
Swift version of #thomas-müller's response
extension UIView {
func firstResponder() -> UIView? {
if self.isFirstResponder() {
return self
}
for subview in self.subviews {
if let firstResponder = subview.firstResponder() {
return firstResponder
}
}
return nil
}
}
I would like to shared with you my implementation for find first responder in anywhere of UIView. I hope it helps and sorry for my english. Thanks
+ (UIView *) findFirstResponder:(UIView *) _view {
UIView *retorno;
for (id subView in _view.subviews) {
if ([subView isFirstResponder])
return subView;
if ([subView isKindOfClass:[UIView class]]) {
UIView *v = subView;
if ([v.subviews count] > 0) {
retorno = [self findFirstResponder:v];
if ([retorno isFirstResponder]) {
return retorno;
}
}
}
}
return retorno;
}
The solution from romeo https://stackoverflow.com/a/2799675/661022 is cool, but I noticed that the code needs one more loop. I was working with tableViewController.
I edited the script and then I checked. Everything worked perfect.
I recommed to try this:
- (void)findFirstResponder
{
NSArray *subviews = [self.tableView subviews];
for (id subv in subviews )
{
for (id cell in [subv subviews] ) {
if ([cell isKindOfClass:[UITableViewCell class]])
{
UITableViewCell *aCell = cell;
NSArray *cellContentViews = [[aCell contentView] subviews];
for (id textField in cellContentViews)
{
if ([textField isKindOfClass:[UITextField class]])
{
UITextField *theTextField = textField;
if ([theTextField isFirstResponder]) {
NSLog(#"current textField: %#", theTextField);
NSLog(#"current textFields's superview: %#", [theTextField superview]);
}
}
}
}
}
}
}
Update: I was wrong. You can indeed use UIApplication.shared.sendAction(_:to:from:for:) to call the first responder demonstrated in this link: http://stackoverflow.com/a/14135456/746890.
Most of the answers here can't really find the current first responder if it is not in the view hierarchy. For example, AppDelegate or UIViewController subclasses.
There is a way to guarantee you to find it even if the first responder object is not a UIView.
First lets implement a reversed version of it, using the next property of UIResponder:
extension UIResponder {
var nextFirstResponder: UIResponder? {
return isFirstResponder ? self : next?.nextFirstResponder
}
}
With this computed property, we can find the current first responder from bottom to top even if it's not UIView. For example, from a view to the UIViewController who's managing it, if the view controller is the first responder.
However, we still need a top-down resolution, a single var to get the current first responder.
First with the view hierarchy:
extension UIView {
var previousFirstResponder: UIResponder? {
return nextFirstResponder ?? subviews.compactMap { $0.previousFirstResponder }.first
}
}
This will search for the first responder backwards, and if it couldn't find it, it would tell its subviews to do the same thing (because its subview's next is not necessarily itself). With this we can find it from any view, including UIWindow.
And finally, we can build this:
extension UIResponder {
static var first: UIResponder? {
return UIApplication.shared.windows.compactMap({ $0.previousFirstResponder }).first
}
}
So when you want to retrieve the first responder, you can call:
let firstResponder = UIResponder.first
Code below work.
- (id)ht_findFirstResponder
{
//ignore hit test fail view
if (self.userInteractionEnabled == NO || self.alpha <= 0.01 || self.hidden == YES) {
return nil;
}
if ([self isKindOfClass:[UIControl class]] && [(UIControl *)self isEnabled] == NO) {
return nil;
}
//ignore bound out screen
if (CGRectIntersectsRect(self.frame, [UIApplication sharedApplication].keyWindow.bounds) == NO) {
return nil;
}
if ([self isFirstResponder]) {
return self;
}
for (UIView *subView in self.subviews) {
id result = [subView ht_findFirstResponder];
if (result) {
return result;
}
}
return nil;
}
Simplest way to find first responder:
func sendAction(_ action: Selector, to target: Any?, from sender: Any?, for event: UIEvent?) -> Bool
The default implementation dispatches the action method to the given
target object or, if no target is specified, to the first responder.
Next step:
extension UIResponder
{
private weak static var first: UIResponder? = nil
#objc
private func firstResponderWhereYouAre(sender: AnyObject)
{
UIResponder.first = self
}
static var actualFirst: UIResponder?
{
UIApplication.shared.sendAction(#selector(findFirstResponder(sender:)), to: nil, from: nil, for: nil)
return UIResponder.first
}
}
Usage:
Just get UIResponder.actualFirst for your own purposes.