I have an inputAccessoryView for a chat app that always remains visible and docked at the bottom of the screen for text input similar to most messaging apps.
When I present an alertController with actionSheet style, the inputAccessoryView animates down off screen as the alert is presented and then back up again when the alert is dismissed. This in turn scrolls my tableView and is undesirable.
This is happening because the chat viewController is giving up firstResponder when the alert is presented.
Is there anyway to present an alertController and not give up firstResponder, or anyway to keep an inputAccessoryView docked at the bottom of the screen when it's view resignsFirstResponder?
The InputAccessoryView sits outside of your ViewController's hierarchy - it's contained in the UITextEffectsWindow whose rootViewController is a UIInputWindowController. Similarly the keyboard is contained in UIRemoteKeyboardWindow and its own UIInputWindowController.
So, if we present the alert from the topmost window or higher (UITextEffectsWindow or UIRemoteKeyboardWindow), it won't resign first responder.
The simplest solution I've found is:
let topViewController = UIApplication.shared.windows.last!.rootViewController!
topViewController.present(alert, animated: true, completion: nil)
Ideally you would safely handle those optionals. A potentially better solution (I've seen some console errors from the previous solution) would be to create a new UIWindow with a higher WindowLevel, make it the key window and visible, and present the alert from there.
Thanks to #Corey W. (give votes to him) we have a solution for this issue. Safer way in Swift 5:
// Instantiate AlertController
let actionSheet = UIAlertController(title: title,
message: "Comment options",
preferredStyle: .actionSheet)
// Add actions
actionSheet.addAction(UIAlertAction(title: "Edit",
style: .default,
handler: { (_: UIAlertAction) in
self.showEditingView(withCommentId: commentId, withCommentText: commentText)
}))
// Present AlertController
if let lastApplicationWindow = UIApplication.shared.windows.last,
let rootViewController = lastApplicationWindow.rootViewController {
rootViewController.present(actionSheet, animated: true, completion: nil)
}
For those looking the Objective-C equivalent of Corey's answer:
UIViewController *objViewController = [UIApplication sharedApplication].windows.lastObject.rootViewController;
[objViewController presentViewController:view animated:YES completion:nil];
Related
I am trying to use InAppSettingsKit from my Swift app (via Swift package dependency to version 3.3.3), and I would like to be able to use the settingsViewControllerDidEnd delegate callback to determine when the user has dismissed the settings dialog, so that I can check for certain conditions that may require additional actions on the user's part.
The Done button was showing up if I pushed the view controller onto a navigation controller, but the code indicates that this method will not fire the Done button delegate callback, so I have been trying to use the present method to show the view controller.
Here is the code that I am using to instantiate and present the settings view controller:
func authenticationSettings(alert: UIAlertAction!) {
let viewController = IASKAppSettingsViewController()
viewController.delegate = self
self.present(viewController, animated: true, completion: nil)
}
And here is what I get, notice no Done button:
I have tried this card method of presenting, and also the full screen method, with no avail.
I tried stepping into the Objective-C code and and from what I could tell, the UIBarButtonItem navigation item was being created and added. Anyone have any ideas on what to try next?
As you may have noticed in the source code, UIBarButtonItem gets added on navigationItem. This item is used only if view controller is part of a navigation controller stack
When you're presenting a new view controller modally it doesn't have a navigation controller in the stack, so to make it work you need to wrap your controller with a UINavigationController:
func authenticationSettings(alert: UIAlertAction!) {
let viewController = IASKAppSettingsViewController()
viewController.delegate = self
let navigationController = UINavigationController(rootViewController: viewController)
self.present(navigationController, animated: true, completion: nil)
}
Similar to Apple's Photo App, I'm using peek and pop on a collectionView of images. When you peek on a cell, you are shown the image on a PhotoViewController that just shows that image in an imageView. Apple provides 4 different preview actions: Copy, Share, Favourite, Delete. If the user hits 'Delete', the preview actions are updated to: Delete and Cancel. The peek image still shows.
In my own code, I can setup the preview actions and delete the photo but am not able to confirm the deletion before it occurs. I also cannot figure out how to keep the peek view open (or reopen it) with the new preview actions of Delete or Cancel.
Question: How can I get the user to confirm the deletion before it happens?
I tried putting a UIAlertController inside the UIPreviewAction but get a warning: "Attempt to present on whose view is not in the window hierarchy!" I tried a few alternatives such as using keyWindow to present the alertController but couldn't get that to work quickly. I assuming I can set another notification to the collection view so that when the peek closes with a 'Delete', an alert controller appears asking the user to confirm the deletion but by that stage the image no longer appears.
Any suggestions? Here are the alternatives I've used so far.
UIPreviewAction *delete = [UIPreviewAction actionWithTitle:#"Delete" style:UIPreviewActionStyleDestructive handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController)
{
//create alertController
...
// 1. present alertController:
[self presentViewController:alertController animated:YES completion:nil];
// 2. Alternatively tried to present alertController using keyWindow
UIViewController *top = [UIApplication sharedApplication].keyWindow.rootViewController;
[top presentViewController:alertController animated:YES completion: nil];
//3. notify the presenting view controller that the user has deleted the image and set up an alert to occur after the peek view closes.
[[NSNotificationCenter defaultCenter] postNotificationName:#"deleteAlert" object:nil];
}
I know it is an old questions, but the solution is quite easy, so posting it here!
Swift 3
let closeTabAction = UIPreviewAction(title: "Close Tab", style: .destructive, handler: { (action, viewController) in
//this is confirmation action
})
let closeTabCancelAction = UIPreviewAction(title: "Cancel", style: .default, handler: { (action, viewController) in
//cancel action, don't need anything here
})
//UIPreviewActionGroup, this is the main action
let closeTabGroup = UIPreviewActionGroup(title: "Close Tab", style: .destructive, actions: [closeTabAction, closeTabCancelAction])
return [closeTabGroup]
Basically, you just need to use the UIPreviewActionGroup to create submenus. Apple Docs
I am creating an iOS app which consists of 2 storyboards.
This is the second one:
I am targeting iOS 7 so I cant use storyboard references. Switch between storyboards is handled programmatically
Using this code:
let viewController:UIViewController = UIStoryboard(name: "Warnings", bundle: nil).instantiateViewControllerWithIdentifier("WarningsInitialView") as UIViewController
self.presentViewController(viewController, animated: true, completion: nil)
I can get to the second storyboard using this. However as you can see second storyboard consists of Tab Controller and I want (on both tabs) back button which will point to the main (previous) storyboard. How should I achieve this?
I have tried using this code:
AvalanchesBackButton.leftBarButtonItem = UIBarButtonItem (title: "< Spät", style: UIBarButtonItemStyle.Plain, target: self, action: "backButtonClicked")
I will be able to achieve what I want by using different view controllers for both views and hardcoding it. But is there a way to do it more cleanly? Like implement a back button on container Tab Controller which will point to previous storyboard?
Thanks in advance
Is your previous ViewController embedded into a Navigation controller? because "pushing" the view controller automatically adds the back button.
self.navigationController?.pushViewController(viewController: UIViewController, animated: Bool)
CNContactViewController for unknowncontact on tap of "Add to Existing Contact" present a view controller. When user selects cancel Navigation bar of CNContactViewController disappears and now there is no way to go back.
There might be better ways to approach this, but this worked for me. Fixed by setting the left bar button item of the navigation bar of my instance of CNContactViewController to a new instance of the cancel button that dismisses the presented CNContactViewController. In code:
let contactVC = CNContactViewController(forUnknownContact: newContact)
contactVC.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel,
target: self, action: #selector(cancelAction))
I then call present and pass in contactVC. Then in cancelAction I called the dismiss function and it worked!
I am editing an app where I added a new UIViewController to configure a multiplayer game in one layout.
I have added two buttons for each player (top button, bot button). Each button generates an alert at the screen (remember it is in landscape orientation ) but the problem is, player 2 do not see the alert rotated, I mean, the alert is supposed to be shown in his orientation, in front of me.
As there any way to do this? I want to rotate an Alert for the player 2 will not see the information of the alert turned.
Here the alert code I have:
#IBAction func ShowAlert(){
let message = "Test"
let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
let action = UIAlertAction(title: "OK", style: .Default, handler: nil)
alert.addAction(action)
presentViewController(alert, animated: true, completion: nil)
}
Here is a way to rotate an alert controller view, although it is not perfect. The controller tries to present based on the orientation and I found that attempting to transform it before it is presented is unsuccessful.
In order to not see "incorrect" orientation initially, you must set the alert to hidden and then make it visible once it is "presented".
let alert // setup
alert.view.hidden = true;
[self presentViewController:c animated:YES completion:^{
alert.view.transform = CGAffineTransformMakeRotation(M_PI);
alert.view.hidden = false;
}];
The only outstanding issue I have with this is that after the user interacts with the alert, it flashes back to the initial transformation (orientation dependent). I currently have not found a solution for this, but will update if I ever do.
Alternatively, using a custom "alert" would solve this problem as you would have more control over its view. This is probably the more reliable approach.
Here is my solution.
presentViewController with no animation to avoid flash when present the alert controller.
[self presentViewController:alert animated:NO completion:^{
[alertController.view setTransform: CGAffineTransformMakeRotation(M_PI_2)];
}];
then create a UIAlertController category named e.g. UIAlertController+Orientation,
hide the view in viewWillDisappear to avoid flash back when dismiss the alert controller.
#import "UIAlertController+Orientation.h"
#implementation UIAlertController (Orientation)
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.view setHidden:YES];
}
- (BOOL) shouldAutorotate {
return NO;
}
#end