iOS How to confirm 'Delete' for UIPreviewActions on images from CollectionView - ios

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

Related

How can I implement the feature in my iOS app?

First Image: This view contains 2 navigation bar with tableview. In the second navigation bar, there is one button.
Second and Third Image: When I click on the navigation button (second) one view will appear like this screenshot and according to the selected title will change on navigation.
Again I I select click on the navigation button, again view will appear and when we select an option according to that option tableview will changes.
What is that pop-up view, how to add or show tableview in that view, how to achieve this feature using Objective-C?
The "popup view' in iOS is a UIAlertController: it has two subtypes: alert and action sheet. Then you need actions(those will represent each option in the actionsheet). Add each action to the action sheet then present it. Easy as that. Each action has a block that is executed when the action is selected. No need to show a tableCiew inside the actionsheet, it should stay as close to native as possible to keep the interface familiar to users. Refer to the human interface guidelines for more information.
UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:#"Title" message:#"Your message here" preferredStyle: UIAlertControllerStyleActionSheet ];
UIAlertAction *action = [UIAlertAction actionWithTitle:#"First item" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSLog(#"first item pressed");
}];
[actionSheet addAction:action];
[self presentViewController:actionSheet animated:YES completion:nil];

inputAccessoryView animating down when alertController (actionSheet) presented

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];

Navigation bar disappear on Cancel of "Add to Exsting Contact" controller

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!

Is it possible to rotate UIAlertController (and Alert) in landscape orientation?

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

Warning :-Presenting view controllers on detached view controllers is discouraged

In my app, I am using a navigation controller. Later on in some view I am using presentViewController for showing a zoomed image.
Also I am not using a Storyboard or nib.
I am getting this error in iOS 7 only. It works fine in iOS 6 and earlier:
Presenting view controllers on detached view controllers is
discouraged
To avoid getting the warning in a push navigation, you can directly use :
[self.view.window.rootViewController presentViewController:viewController animated:YES completion:nil];
And then in your modal view controller, when everything is finished, you can just call :
[self dismissViewControllerAnimated:YES completion:nil];
Wait for viewDidAppear():
This error can also arise if you are trying to present view controller before view actually did appear, for example presenting view in viewWillAppear() or earlier.
Try to present another view after viewDidAppear() or inside of it.
The reason of this warning is i was presenting a view controller over a small view that is not full size view. Given below is the image of my project. where on click on four option above. User navigate to different childviewcontroller's view.(it works like tabViewcontroller). But the childviewcontroller contains view of small size. So if we present a view from childviewcontroller it gives this warning.
And to avoid this, you can present a view on childviewcontroller's parent
[self.parentViewController presentViewController:viewController animated:YES completion:nil];
In my case, I've a sampleViewController's view added as a subview, then tries to present a popover from the view of sampleViewController (here self instead a UIViewController instance):
[self.view addSubview:sampleViewController.view];
The right way should be below:
// make sure the vc has been added as a child view controller as well
[self addChildViewController:sampleViewController];
[self.view addSubview:sampleViewController.view];
[sampleViewController didMoveToParentViewController:self];
B.t.w., this also works for the case that present a popover form a tableview cell, you just need to make sure the tableview controller has been added as child view controller as well.
Swift 3
For anyone stumbling on this, here is the swift answer.
self.parent?.present(viewController, animated: true, completion: nil)
I think that the problem is that you do not have a proper view controller hierarchy. Set the rootviewcontroller of the app and then show new views by pushing or presenting new view controllers on them. Let each view controller manage their views. Only container view controllers, like the tabbarviewcontroller, should ever add other view controllers views to their own views. Read the view controllers programming guide to learn more on how to use view controllers properly. https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/
I have almost the same problem. The reason was that I tried to present "some" controller on another and after animation was completed I was setting presented controller as root. After this operation all further controllers presenting bring me to the warning: "Presenting view controllers on detached view controllers is discouraged". And I solve this warning just settings "some" controller as root without any presentation at the begin.
Removed:
[[self rootController] presentViewController:controller animated:YES completion:^{
[self window].rootViewController = controller;
[[self window] makeKeyAndVisible];}];
Just make as root without any presentation:
[[self window] setRootViewController:controller];
One of the solution to this is if you have childviewcontroller So you simply presentviewcontroller on its parent by given
[self.parentViewController presentViewController:viewController animated:YES completion:nil];
And for dismiss use the same dismissview controller.
[self dismissViewControllerAnimated:YES completion:nil];
This is perfect solution works for me.
Use [self.navigationController presentViewController:xxx animated:YES completion:nil] in iOS 8.
In Swift 4.1 and Xcode 9.4.1
The solution is
DispatchQueue.main.async(execute: {
self.present(alert, animated: true)
})
If write like this i'm getting same error
let alert = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: { action in
})
alert.addAction(defaultAction)
present(alert, animated: true, completion: nil)
I'm getting same error
Presenting view controllers on detached view controllers is discouraged <MyAppName.ViewController: 0x7fa95560Z070>.
Complete solution is
let alert = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: { action in
})
alert.addAction(defaultAction)
//Made Changes here
DispatchQueue.main.async(execute: {
self.present(alert, animated: true)
})
Try this code
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:<your ViewController object>];
[self.view.window.rootViewController presentViewController:navigationController animated:YES completion:nil];
Try presenting on TabBarController if it is a TabBarController based app .
[self.tabBarController presentViewController:viewController animated:YES completion:nil];
Reason could be self is child of TabBarController and you are trying to present from a ChildViewController.
Yes, I also faced the same warning message while displaying an Alert controller which was in another view. Later on I avoided this by presenting the alert controller from the parent view controller as below:
[self.parentViewController presentViewController:alertController animated:YES completion:nil];
you need to add the view controller that will present the new controller as a child of the parent view controller.
Let's say you have yourMainViewController, then you add a new controller called controllerA, and then you want to present a new controller called controllerB from controllerA
you have to write something like this:
[self addChildViewController:controllerA]; //self is yourMainViewController
[self.view addsubView:controllerA.view];
and within controllerA you can present the new controller without warnings
[self presentViewController:controllerB animated:YES completion:nil]; //self is controllerA
I reached on this thread where I have a Custom Navigation Bar and I was calling an AlertViewController through it.
I had to add it as a child to my main view controller. Then I could call present it without any warning.
You should add your Zoomed Image View Controller as a child of the main ViewController.
(e.g)
[self addChildViewController:ZoomedImageViewController];
Then you'd be able to call your ZoomedImageViewController
[self presentViewController:ZoomedImageViewController];
Make sure you have a root view controller to start with. You can set it in didFinishLaunchingWithOptions.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[window setRootViewController:viewController];
}
Lots of reasons for this warning. Mine is because I have a segue connected from a ViewController to another that will be presented modally. But, the ViewController I am presenting from is being dynamically generated by a PageViewController. Which is why it is detached in the Storyboard. My app isn't going to crash because of it; but I would like to silence the warning.
It depends if you want to show your alert or something similar in anywhere of kind UIViewController.
You can use this code example:
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"Alert" message:#"Example" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:#"Cancel" style:UIAlertActionStyleDefault handler:nil];
[alert addAction:cancelAction];
[[[[[UIApplication sharedApplication] delegate] window] rootViewController] presentViewController:alert animated:true completion:nil];
Many answers are right.
Check your presentingViewController have parentViewController or not.
If no, add it to somewhere it should be added
else, check it's parentViewController has parentViewController recursively until every viewController has parent
This issue happened to me when my co-worker add a AViewController to BViewController. Somehow, he just add the AViewController's view to BViewController's view.
Fixed by add bViewController.addChild(aViewController)

Resources