Display UIAlertController from UIView/NSObject class - objective-c

I have working iOS application
In order to support iOS8, I am replacing UIAlertView/UIActionSheet with
UIAlertController.
Problem :
For display UIAlertController I need presentViewController
method of UIViewController class.
But UIAlertView is display from classes which are inherited from
UIView or NSObject,
I can not get [self presentViewController...] method for obvious reason.
My Work :
I tried getting rootViewController form current window and display UIAlertController.
[[[UIApplication sharedApplication] keyWindow].rootViewController presentViewController ...]
but have some rotation problems like if my current view controller do not have rotation support
it will rotate if UIAlertController is open.
Question :
Did any one faced same problem and have safe solution ?
if yes please provide me some example or give some guide

I solved an essentially similar problem today. Like Jageen, I ran into a situation where I wanted to present a UIAlertController but from a non-UIViewController class. In my case, I wanted an alert to pop up when the failure block of a HTTP request is run.
This is what I used and unlike our friend here, it worked quite perfectly for me.
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(errorAlert, animated: true, completion: nil)

The better solution for UIView classes is below
ObjectiveC
UIViewController *currentTopVC = [self currentTopViewController];
currentTopVC.presentViewController.........
- (UIViewController *)currentTopViewController
{
UIViewController *topVC = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
while (topVC.presentedViewController)
{
topVC = topVC.presentedViewController;
}
return topVC;
}
Swift
var topVC = UIApplication.sharedApplication().keyWindow?.rootViewController
while((topVC!.presentedViewController) != nil){
topVC = topVC!.presentedViewController
}
topVC?.presentViewController........

My solution is below:
Swift
class alert {
func msg(message: String, title: String = "")
{
let alertView = UIAlertController(title: title, message: message, preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: "Done", style: .Default, handler: nil))
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertView, animated: true, completion: nil)
}
}
Here is sample usage:
let Alert = alert()
Alert.msg("My alert (without title)")
Alert.msg("This is my alert", title: "Warning!")

It looks like you are currently (pre-iOS8) triggering an alert view from within your view object. That's pretty bad practice, as in general alerts should be triggered from actions and logic. And that code should live in controllers.
I suggest you refactor your current code to move the logic that triggers the alert to the correct controller, and then you can easily upgrade to iOS 8 by using self as the controller.
If instead you're calling the alert from an outside object, then pass in the controller to the method that calls the alert. Somewhere upstream you must have knowledge of the controller.

For Swift 4
UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil)
For Swift 5
UIApplication.shared.windows.last?.rootViewController?.present(alert, animated: true)

I had a situation where a subview contains a button to dismiss it. I present an alert to confirm the action. It sends a message to the delegate - which is the view controller containing the subview - to remove the subview
Originally I presented a UIAlertView from a UIView. Refactoring for UIAlertController, since the UIAlertController can't present itself like a UIAlertView can, I came up with the following (in Swift; easily translated to ObjC):
Add a protocol to the subview:
protocol MySubviewDelegate {
// called when user taps subview/delete button
// or, you could call it from a gesture handler, etc.
func displayAlert(alert : UIAlertController)
// called when user confirms delete from the alert controller
func shouldRemoveSubview(sender : AnyObject)
}
Add a delegate for the subview, and add a handler for the button/gesture tap:
class MySubview : UIView {
var subviewDelegate : MySubviewDelegate!
...
func handleTap(sender : AnyObject) {
// set up the alert controller here
var alert = UIAlertController(title: "Confirm Delete",
message: "This action is permanent. Do you wish to continue?",
preferredStyle: UIAlertControllerStyle.Alert)
// Cancel action
// nil handler means "no action if Cancel button selected"
alert.addAction(UIAlertAction(title: "Cancel",
style: UIAlertActionStyle.Cancel,
handler: nil))
// Confirm action
alert.addAction(UIAlertAction(title: "Confirm",
style: UIAlertActionStyle.Default,
handler: { (action : UIAlertAction!) -> Void in
// call delegate method to perform confirmed action, - i.e. remove
self.subviewDelegate.shouldRemoveSubview(self)
}))
// call delegate method to display alert controller
// send alert object to delegate
self.subviewDelegate.displayAlert(alert)
}
}
Set the calling UIViewController as the delegate of the subview, e.g., in its viewDidLoad() method, and include protocol methods:
class viewController : UIViewController, MySubviewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.subviewDelegate = self
...
}
func displayAlert(alert : UIAlertController) {
presentViewController(alert, animated: true, completion: nil)
}
func shouldRemoveSubview(sender : AnyObject) {
// cast as UIView / MySubview subclass
var subview = sender as MySubview
// remove the subview / perform the desired action
subview.removeFromSuperview()
...
}
...
}
This avoids the need to find the topmost view controller, or pass references to view controllers to subviews (other than in an object/delegate relationship).

In Swift 3:
UIApplication.shared.keyWindow?.rootViewController?.present(alertView, animated: true, completion: nil)

For Display UIAlertController in NSObject Class use below Code.
UIAlertController * popup = [UIAlertController
alertControllerWithTitle:nil
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction* cancel = [UIAlertAction
actionWithTitle:#"Cancel"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
[popup dismissViewControllerAnimated:YES completion:nil];
}];
[popup addAction:cancel];
UIViewController *rootViewController = [[Helper shareInstance] topViewController];
[rootViewController presentViewController:popup animated:YES completion:nil];
// Put Below Method in Your Global Helper Class.
- (UIViewController *)topViewController {
return [self topViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
- (UIViewController *)topViewController:(UIViewController *)rootViewController {
if (rootViewController.presentedViewController == nil) {
return rootViewController;
}
if ([rootViewController.presentedViewController isMemberOfClass:[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];
}

In general, alerts should be handled in the view controller. Here's an example of the code required:
Swift 3
private func displayError(message: String) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert)
let okayAction = UIAlertAction(title: "Okay", style: .default, handler: nil)
alertController.addAction(okayAction)
present(alertController, animated: true, completion: nil)
}

I know the question has been already answered... But As I am also looking for the same issue, but none of the above solutions worked for me.
So after doing many try and error finally, I found a very easy and sustainable solution.
func showError(title: String?, error: String?) {
DispatchQueue.main.async(execute: {
let alert = UIAlertController(title: title, message: error, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
CommonMethods.instance.topMostController()?.present(alert, animated: true, completion: nil)
})
}
static let instance = CommonMethods()
fileprivate func topMostController() -> UIViewController? {
var presentedVC = UIApplication.shared.keyWindow?.rootViewController
while let pVC = presentedVC?.presentedViewController {
presentedVC = pVC
}
if presentedVC == nil { }
return presentedVC
}

Related

How do I get a back button in pushed ViewController (Xcode)

I have a viewController with a menu button set as navigationItem like this:
self.navigationItem.setLeftBarButton(leftButton, animated: false).
It brings up a drawer menu, which is fine in its context. But what if I want to push the viewController from elsewhere in the project where a back button is more appropriate instead of a menu button? That is, I just want to go back to the previous viewController instead of bringing up the menu. How do I get rid of the menu button and instead get the back button in that particular instance?
Here is the code for the class. As you can see, the navigationItem is set here.
override func viewDidLoad() {
super.viewDidLoad()
self.setUserInterfaceStyleLight()
loginStoryboard = UIStoryboard(name: "StoryboardOne", bundle: nil)
let leftButton = UIBarButtonItem(image: UIImage(named: "menu-icon"), style: .plain, target: self, action: #selector(self.leftSideMenuButtonPressed(_:)))
self.navigationItem.setLeftBarButton(leftButton, animated: false)
self.segmentController.tintColor = .white
if showContacts == true {
lastSegmentViewed = 1
}
Analytics.logEvent("contact_book", parameters: nil)
NotificationCenter.default.addObserver(self, selector: #selector(userDidLogin(_:)), name: NSNotification.Name.UserDidLogin, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(applicationAndViewWasResumed(_:)), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
self.view.backgroundColor = ThemTemplate.shared.getThem().secondaryColor
if #available(iOS 13.0, *) {
segmentController.backgroundColor = UIColor.gray
} else {
segmentController.backgroundColor = UIColor.clear
}
}
Is it possible to hide and disable the navigationItem from the previous viewController or change it to a back button?
This is how I push the viewController:
UIStoryboard *containerStoryboard = [UIStoryboard storyboardWithName:#"Login" bundle:nil];
BaseViewController *v = [containerStoryboard instantiateViewControllerWithIdentifier:#"base"];
[self.navigationController pushViewController:v animated:YES];
In the class before the push to the new viewController I tried something like:
v.navigationItem.leftBarButtonItem = nil;
v.navigationItem.hidesBackButton = NO;
...which did nothing (and yes, setting it to nil is dumb, just wanted to see if something happened and it did not).
The viewController I'm pushing from is not embedded in a navigationController, if that helps. I tried embedding it on one, but with the same results.
Left me know if there is anything else that can be helpful.
Add a Bool var in BaseViewController:
class BaseViewController: UIViewController {
var bShowMenu: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
self.setUserInterfaceStyleLight()
loginStoryboard = UIStoryboard(name: "StoryboardOne", bundle: nil)
if bShowMenu {
let leftButton = UIBarButtonItem(image: UIImage(named: "menu-icon"), style: .plain, target: self, action: #selector(self.leftSideMenuButtonPressed(_:)))
self.navigationItem.setLeftBarButton(leftButton, animated: false)
}
self.segmentController.tintColor = .white
if showContacts == true {
lastSegmentViewed = 1
}
Analytics.logEvent("contact_book", parameters: nil)
NotificationCenter.default.addObserver(self, selector: #selector(userDidLogin(_:)), name: NSNotification.Name.UserDidLogin, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(applicationAndViewWasResumed(_:)), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
self.view.backgroundColor = ThemTemplate.shared.getThem().secondaryColor
if #available(iOS 13.0, *) {
segmentController.backgroundColor = UIColor.gray
} else {
segmentController.backgroundColor = UIColor.clear
}
}
}
then, when you instantiate the controller, tell it whether or not to use the "menu" button:
UIStoryboard *containerStoryboard = [UIStoryboard storyboardWithName:#"Login" bundle:nil];
BaseViewController *v = [containerStoryboard instantiateViewControllerWithIdentifier:#"base"];
// if you do NOT want the menu button
v.bShowMenu = false
[self.navigationController pushViewController:v animated:YES];

Dismissing all presented ViewControlelrs in a UINavigationController hierarchically

I was looking for a way to dismiss all the modally presented viewControllers in a UINavigationController hierarchically without knowing the name of them. so I ended up to the while loop as follow:
Swift
while(navigationController.topViewController != navigationController.presentedViewController) {
navigationController.presentedViewController?.dismiss(animated: true, completion: nil)
}
Objective-c
while(![self.navigationController.topViewController isEqual:self.navigationController.presentedViewController]) {
[self.navigationController.presentedViewController dismissViewControllerAnimated:YES completion:nil];
}
I want to dismiss all the presentedControllers one by one till the presentedViewController and topViewcontroller become equal.
the problem is that the navVC.presentedViewController doesn't changed even after dismissing.
It remains still the same even after dismissing and I end up to an infiniteLoop.
Does anyone knows where is the problem?
In my case nothing works but:
func dismissToSelf(completion: (() -> Void)?) {
// Collecting presented
var presentedVCs: [UIViewController] = []
var vc: UIViewController? = presentedViewController
while vc != nil {
presentedVCs.append(vc!)
vc = vc?.presentedViewController
}
// Dismissing all but first
while presentedVCs.count > 1 {
presentedVCs.last?.dismiss(animated: false, completion: nil)
presentedVCs.removeLast()
}
// Dismissing first with animation and completion
presentedVCs.first?.dismiss(animated: true, completion: completion)
}
I've found the answer. I can dismiss all presentedViewControllers on a navigationController by:
navigationController.dismiss(animated: true, completion: nil)
It keeps the topViewController and dismiss all other modals.
Form your question I understood that you want to dismiss all view controllers above the root view controller. For that you can do it like this:
self.view.window!.rootViewController?.dismiss(animated: false, completion: nil)
Not need to used self.navigationController.presentedViewController.
Might be help! my code is as follows:
Objective-c
[self dismissViewControllerAnimated:YES completion:^{
}];
// Or using this
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:nil];
});
Please check this code
-(void)dismissModalStack {
UIViewController *vc = self.window.rootViewController;
while (vc.presentedViewController) {
vc = vc.presentedViewController;
[vc dismissViewControllerAnimated:false completion:nil];
}
}
Glad to see you have found the answer, and I've done this by another way.
You can create a BaseViewController(actually lots of app do that), and defined a property like 'presentingController' in appdelegate that indicate the presenting ViewController, then in the viewWillAppear method, set the property so that it always indicate the top view controller.
-(void)viewWillAppear:(BOOL)animated{
AppDelegate *delegate=(AppDelegate *)[[UIApplication sharedApplication]delegate];
delegate.presentingController = self;
}
All the class inherited from BaseViewController will call it. When you want to dismiss all the controller, just loop as follow:
- (void)clickButton:(id)sender {
AppDelegate *delegate=(AppDelegate *)[[UIApplicationsharedApplication]delegate];
if (delegate.presentingController)
{
UIViewController *vc =self.presentingViewController;
if ( !vc.presentingViewController ) return;
while (vc.presentingViewController)
{
vc = vc.presentingViewController;
}
[vc dismissViewControllerAnimated:YEScompletion:^{
}];
}
}
Hope this will help you :)
I had a similar issue of deleting/dismissing existing/previous push notification when a new push notification arrives where different pictures are sent as a push notification.
In my situation, using Swift 5, I wanted to delete/dismiss previous push notification and display a new push notification all by itself regardless whether the user acknowledged the previous notification or not (i.e. without user's acknowledgement).
I tried Kadian's recommendation with a minor change and it worked flawlessly.
Here is my NotificationDelegate.swift
import UIKit
import UserNotifications
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .sound, .badge])
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
defer { completionHandler() }
guard response.actionIdentifier == UNNotificationDefaultActionIdentifier else {return}
let payload = response.notification.request.content
let pn = payload.body
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: pn)
//***Below cmd will erase previous push alert***
self.window!.rootViewController?.dismiss(animated: false, completion: nil)
//Below cmd will display a newly received push notification
self.window!.rootViewController!.present(vc, animated: false)
}
}

Hiding the master view controller with UISplitViewController in iOS8

I have an iOS7 application, which was based on the Xcode master-detail template, that I am porting to iOS8. One area that has changed a lot is the UISplitViewController.
When in portrait mode, if the user taps on the detail view controller, the master view controller is dismissed:
I would also like to be able to programmatically hide the master view controller if the user taps on a row.
In iOS 7, the master view controller was displayed as a pop-over, and could be hidden as follows:
[self.masterPopoverController dismissPopoverAnimated:YES];
With iOS 8, the master is no longer a popover, so the above technique will not work.
I've tried to dismiss the master view controller:
self.dismissViewControllerAnimated(true, completion: nil)
Or tell the split view controller to display the details view controller:
self.splitViewController?.showDetailViewController(bookViewController!, sender: self)
But nothing has worked so far. Any ideas?
Extend the UISplitViewController as follows:
extension UISplitViewController {
func toggleMasterView() {
let barButtonItem = self.displayModeButtonItem()
UIApplication.sharedApplication().sendAction(barButtonItem.action, to: barButtonItem.target, from: nil, forEvent: nil)
}
}
In didSelectRowAtIndexPath or prepareForSegue, do the following:
self.splitViewController?.toggleMasterView()
This will smoothly slide the master view out of the way.
I got the idea of using the displayModeButtonItem() from this post and I am simulating a tap on it per this post.
I am not really happy with this solution, since it seems like a hack. But it works well and there seems to be no alternative yet.
Use preferredDisplayMode. In didSelectRowAtIndexPath or prepareForSegue:
self.splitViewController?.preferredDisplayMode = .PrimaryHidden
self.splitViewController?.preferredDisplayMode = .Automatic
Unfortunately the master view abruptly disappears instead of sliding away, despite the documentation stating:
If changing the value of this property leads to an actual change in
the current display mode, the split view controller animates the
resulting change.
Hopefully there is a better way to do this that actually animates the change.
The code below hides the master view with animation
UIView.animateWithDuration(0.5) { () -> Void in
self.splitViewController?.preferredDisplayMode = .PrimaryHidden
}
I was able to have the desired behavior in a Xcode 6.3 Master-Detail Application (universal) project by adding the following code in the MasterViewController's - prepareForSegue:sender: method:
if view.traitCollection.userInterfaceIdiom == .Pad && splitViewController?.displayMode == .PrimaryOverlay {
let animations: () -> Void = {
self.splitViewController?.preferredDisplayMode = .PrimaryHidden
}
let completion: Bool -> Void = { _ in
self.splitViewController?.preferredDisplayMode = .Automatic
}
UIView.animateWithDuration(0.3, animations: animations, completion: completion)
}
The complete - prepareForSegue:sender: implementation should look like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let object = objects[indexPath.row] as! NSDate
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
if view.traitCollection.userInterfaceIdiom == .Pad && splitViewController?.displayMode == .PrimaryOverlay {
let animations: () -> Void = {
self.splitViewController?.preferredDisplayMode = .PrimaryHidden
}
let completion: Bool -> Void = { _ in
self.splitViewController?.preferredDisplayMode = .Automatic
}
UIView.animateWithDuration(0.3, animations: animations, completion: completion)
}
}
}
}
Using traitCollection may also be an alternative/supplement to displayMode in some projects. For example, the following code also works for a Xcode 6.3 Master-Detail Application (universal) project:
let traits = view.traitCollection
if traits.userInterfaceIdiom == .Pad && traits.horizontalSizeClass == .Regular {
let animations: () -> Void = {
self.splitViewController?.preferredDisplayMode = .PrimaryHidden
}
let completion: Bool -> Void = { _ in
self.splitViewController?.preferredDisplayMode = .Automatic
}
UIView.animateWithDuration(0.3, animations: animations, completion: completion)
}
Swift 4 update:
Insert it into prepare(for segue: ...
if splitViewController?.displayMode == .primaryOverlay {
let animations: () -> Void = {
self.splitViewController?.preferredDisplayMode = .primaryHidden
}
let completion: (Bool) -> Void = { _ in
self.splitViewController?.preferredDisplayMode = .automatic
}
UIView.animate(withDuration: 0.3, animations: animations, completion: completion)
}
Modifying the answers above this is all I needed in a method of my detail view controller that configured the view:
[self.splitViewController setPreferredDisplayMode:UISplitViewControllerDisplayModePrimaryHidden];
Of course it lacks the grace of animation.
try
let svc = self.splitViewController
svc.preferredDisplayMode = UISplitViewControllerDisplayMode.PrimaryHidden
My solution in the Swift 1.2
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
var screen = UIScreen.mainScreen().currentMode?.size.height
if (UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Pad) || screen >= 2000 && UIDevice.currentDevice().orientation.isLandscape == true && (UIDevice.currentDevice().userInterfaceIdiom == .Phone){
performSegueWithIdentifier("showDetailParse", sender: nil)
self.splitViewController?.preferredDisplayMode = UISplitViewControllerDisplayMode.PrimaryHidden
} else if (UIDevice.currentDevice().userInterfaceIdiom == .Phone) {
performSegueWithIdentifier("showParse", sender: nil)
}
}
for iPad add Menu button like this
UIBarButtonItem *menuButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:#"burger_menu"]
style:UIBarButtonItemStylePlain
target:self.splitViewController.displayModeButtonItem.target
action:self.splitViewController.displayModeButtonItem.action];
[self.navigationItem setLeftBarButtonItem:menuButtonItem];
This work great with both landscape and portrait mode.
To programmatically close the popover vc you just need to force the button action like this
[self.splitViewController.displayModeButtonItem.target performSelector:appDelegate.splitViewController.displayModeButtonItem.action];
Very similar to the method by phatmann, but a bit simpler in Swift 5. And it's not technically a 'hack', as it is what the iOS doc suggested.
In your prepareForSegue or other methods that handle touches, in
let barButton = self.splitViewController?.displayModeButtonItem
_ = barButton?.target?.perform(barButton?.action)
According to Apple, the splitViewController's displayModeButtonItem is set up for you to display the master view controller in a way that suits your device orientation. That is, .preferHidden in portrait mode.
All there's to do is to press the button, programatically. Or you can put it in an extension to UISplitViewController, like phatmann did.

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()
}
}

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.