previewActionItems not getting called (Force touch action) - ios

I am using below code to add force touch preview actions... Peek and pop view works great, only actions are not showing... Please help my code is not getting executed for some reason, take a look:
- (NSArray<id<UIPreviewActionItem>> *)previewActionItems {
if (_previewActions == nil) {
UIPreviewAction *rateAction = [UIPreviewAction actionWithTitle:#"Rate" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
EmbededRateViewController *embededRVC = [[EmbededRateViewController alloc]initWithEmployerToRate:self.employersArray[0]];
embededRVC.view.bounds = CGRectMake(0, 0, self.view.frame.size.width - 40, 210);
[embededRVC setPopinTransitionStyle:BKTPopinTransitionStyleSnap];
BKTBlurParameters *blurParameters = [[BKTBlurParameters alloc] init];
blurParameters.tintColor = [UIColor colorWithWhite:0 alpha:0.5];
blurParameters.radius = 0.3f; // 0.3
[embededRVC setBlurParameters:blurParameters];
[embededRVC setPopinTransitionDirection:BKTPopinTransitionDirectionTop];
[self.collectionView setScrollEnabled:NO];
[self presentPopinController:embededRVC animated:YES completion:^{
NSLog(#"Popin presented !");
}];
}];
UIPreviewAction *commentAction = [UIPreviewAction actionWithTitle:#"Comment" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
NewCommentViewController *ncvc = [[NewCommentViewController alloc]initWithEmployer:self.employersArray[0]];
[self presentViewController:ncvc animated:YES completion:nil];
}];
UIPreviewAction *reportAction = [UIPreviewAction actionWithTitle:#"Report" style:UIPreviewActionStyleDestructive handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
ReportEmployerViewController *reportEmpVC = [[ReportEmployerViewController alloc]initWithEmployer:self.employersArray[0]];
[self presentViewController:reportEmpVC animated:YES completion:nil];
}];
UIPreviewAction *cancelAction =
[UIPreviewAction actionWithTitle:#"Cancel"
style:UIPreviewActionStyleSelected
handler:^(UIPreviewAction *action,
UIViewController *previewViewController){
}];
_previewActions = #[commentAction, rateAction, reportAction, cancelAction];
}
return _previewActions;
}

I was facing the same issue was was stuck for long.
The mistake that i did was adding this method in the Caller View Controller.
DO NOT add this method in the caller view controller , add it in the called view controller instead.
For example if you are presenting View Controller B (called) on force touch of View Controller A(caller), then add this method to the View Controller B (called).
and was too obvious , because we are handling the button actions in View controller B.
Hope it helps you.
All the best.

Related

uncaught exception 'NSGenericException: application has presented a UIAlertController of style UIAlertControllerStyleActionSheet

I am working on an app which i run on iPhone works well but when i am trying to run on iPad it crashes
Here is my code:
- (void)parseCountryStates:(NSDictionary *)json
{
countryPickerView.hidden = TRUE;
NSDictionary *listing = [json objectForKey:#"country"];
countryArray = [listing allValues];
countryIDArray = [listing allKeys];
[countryPickerView reloadAllComponents];
alertController = [UIAlertController
alertControllerWithTitle:#"Select Service Type"
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
int count = (int)[countryPickerView numberOfRowsInComponent:0];
for (int i = 0; i < count; i++)
{
UIAlertAction* button = [UIAlertAction
actionWithTitle:[[countryPickerView delegate] pickerView:countryPickerView titleForRow:i forComponent:0]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
countryField.text = [action title];
countryStr = countryField.text;
if ([countryArray containsObject:countryStr]) {
countryidStr = [countryIDArray objectAtIndex:[countryArray indexOfObject:countryStr]];
NSLog(#"CountryidStr %#",countryidStr);
[self getState];
}
}];
[alertController addAction:button];
}
UIAlertAction* cancel = [UIAlertAction
actionWithTitle:#"Cancel"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action)
{
// UIAlertController will automatically dismiss the view
}];
[alertController addAction:cancel];
[self presentViewController:alertController animated:true completion:nil];
}
I am sharing the crash log of it
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Your application has presented a UIAlertController () of
style UIAlertControllerStyleActionSheet. The modalPresentationStyle of
a UIAlertController with this style is UIModalPresentationPopover. You
must provide location information for this popover through the alert
controller's popoverPresentationController. You must provide either a
sourceView and sourceRect or a barButtonItem. If this information is
not known when you present the alert controller, you may provide it in
the UIPopoverPresentationControllerDelegate method
-prepareForPopoverPresentation.
Add source view and source rect to your alertController.
[[alertController popoverPresentationController] setSourceView:self.view];
[[alertController popoverPresentationController] setSourceRect:CGRectMake(0,0,1,1)];
[[alertController popoverPresentationController] setPermittedArrowDirections:UIPopoverArrowDirectionUp];
[self presentViewController:alertController animated:true completion:nil];
actully on ipad alertcontrollers are not allowed instead you can use pop overs to diaplay kind of alert
Programtically
UIViewController *newViewCont = [[UIViewController alloc] init];
newViewCont.view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 180, 180)];
newViewCont.modalPresentationStyle = UIModalPresentationPopover;
[self presentViewController:newViewCont animated:YES completion:nil];
UIPopoverPresentationController *pop = [newViewCont popoverPresentationController];
pop.permittedArrowDirections = UIPopoverArrowDirectionAny;
[pop setSourceView:myButton];
[pop setSourceRect:myButton.bounds];
Using storyboards
// grab the view controller we want to show
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *controller = [storyboard instantiateViewControllerWithIdentifier:#"Pop"];
// present the controller
// on iPad, this will be a Popover
// on iPhone, this will be an action sheet
controller.modalPresentationStyle = UIModalPresentationPopover;
[self presentViewController:controller animated:YES completion:nil];
// configure the Popover presentation controller
UIPopoverPresentationController *popController = [controller popoverPresentationController];
popController.permittedArrowDirections = UIPopoverArrowDirectionUp;
popController.delegate = self;
// in case we don't have a bar button as reference
popController.sourceView = self.view;
popController.sourceRect = CGRectMake(30, 50, 10, 10);
dismiss popover
[self dismissViewControllerAnimated:YES completion:nil];
There’s a new protocol called the UIPopoverPresentationControllerDelegate that is called upon dismissal and position change due to rotation or interface changes. We can even prevent a Popover from being dismissed if we wish. Here are the three methods we can implement:
- (void)popoverPresentationControllerDidDismissPopover:(UIPopoverPresentationController *)popoverPresentationController {
// called when a Popover is dismissed
}
- (BOOL)popoverPresentationControllerShouldDismissPopover:(UIPopoverPresentationController *)popoverPresentationController {
// return YES if the Popover should be dismissed
// return NO if the Popover should not be dismissed
return YES;
}
- (void)popoverPresentationController:(UIPopoverPresentationController *)popoverPresentationController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing _Nonnull *)view {
// called when the Popover changes position
}
Don’t forget to conform to the protocol, and set the delegate to your reacting class.
UIPopovers are not allowed on iPads, but there is a way you can do this as other answers have indicated. Here is a Swift 5.x version.
let ac = UIAlertController(title: "Some title goes here", message: nil, preferredStyle: .actionSheet)
ac.addAction(UIAlertAction(title: "Some button name", style: .default) {
[unowned self] _ in
// stuff to do goes here
self.doSomething()
})
// iPad specific code
ac.popoverPresentationController?.sourceView = self.view
let xOrigin = nil // Replace this with one of the lines at the end
let popoverRect = CGRect(x: xOrigin, y: 0, width: 1, height: 1)
ac.popoverPresentationController?.sourceRect = popoverRect
ac.popoverPresentationController?.permittedArrowDirections = .up
present(ac, animated: true)
Replacing the the let xOrigin = nil line with one of the ones below will control where the popover appears below the navigation bar. You can also change x and y to the proper value in the bounds or frame of a different element if you have control that is below the nav bar on an iPad.
Top Left
let xOrigin = 0
Top Middle
let xOrigin = self.view.bounds.width / 2
Top Right
let xOrigin = self.view.bounds.width
Hope this helps.

UIPopoverPresentationController being cut on iPad iOS 10

Im trying to show a list inside a popover controller using
UIAlertController. Works well on iOS 8, 9. On iOS 10 ipad it gets cut. Although I am adding actions and verified it is being added but the popover gets cut. Please suggest . Below is my code -
UIAlertController * view= [UIAlertController
alertControllerWithTitle:nil
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
if(IS_IPAD) {
UIPopoverPresentationController *popPresenter = [view
popoverPresentationController];
[view setModalPresentationStyle:UIModalPresentationPopover];
[view setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
popPresenter.sourceView = self.view;
popPresenter.sourceRect = CGRectMake(70, 0, self.view.frame.size.width, self.view.frame.size.height);
popPresenter.barButtonItem = self.navigationItem.leftBarButtonItem;
}
// Add Cacel Action
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(#"إلغاء", #"'Cancel' title for button")
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
[view dismissViewControllerAnimated:YES completion:nil];
}];
[view addAction:cancelAction];
[self.liveFeed.filter.filtersItems enumerateObjectsUsingBlock:^(DPFilteritem *item, NSUInteger idx, BOOL * _Nonnull stop) {
if (![item.filterName isKindOfClass:[NSNull class]] && item.filterName.length > 0) {
UIAlertAction* alertAction = [UIAlertAction
actionWithTitle:item.filterName
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
//Do some thing here
NSUInteger index = [[view actions]indexOfObject:action];
[view dismissViewControllerAnimated:YES completion:nil];
[self didSelectAlertItemAtIndex:index];
}];
[view addAction:alertAction];
}
}];
view.view.tintColor = [UIColor blackColor];
dispatch_async(dispatch_get_main_queue(), ^ {
[self presentViewController:view animated:YES completion:^{
//fix for iOS 9 - known issue in iOS9. You need to set tint color in completion handler.
view.view.tintColor = [UIColor blackColor];
}];
});
iOS 10 - ipad -
iOS 9 - ipad -

Unbalanced calls to begin/end appearance transitions for <UIViewController: 0x176c0bd0> within a Navigation Controller

I am trying to write a custom segue and have come across this error
Unbalanced calls to begin/end appearance transitions for UIViewController: 0x176c0bd0
The help button is connected to the almost empty ViewController - and the exit button unwinds the segue
All the controllers are embedded in a navigation Controller.
I've read through various posts here where people have encountered the same problem, but the solution varies a lot, and I still haven't found the right solution. I think it is because I am calling the custom segue from within a Navigation Controller, but that my code doesn't reflect that. I've followed this tutorial to create the custom segue http://blog.dadabeatnik.com/2013/10/13/custom-segues/
The initial controller has the following methods:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue isKindOfClass:[ICIHelpSegue class]]) {
((ICIHelpSegue *)segue).originatingPoint = self.help.center;
}
}
- (IBAction)unwindFromViewController:(UIStoryboardSegue *)sender {
}
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier {
ICIUnwindHelpSegue *segue = [[ICIUnwindHelpSegue alloc] initWithIdentifier:identifier source:fromViewController destination:toViewController];
segue.targetPoint = self.help.center;
return segue;
}
The ICIHelpSegue class is the following interface:
#interface ICIHelpSegue : UIStoryboardSegue
#property CGPoint originatingPoint;
#property CGPoint targetPoint;
#end
And the implementation file looks like this:
#implementation ICIHelpSegue
- (void)perform {
UIViewController *sourceViewController = self.sourceViewController;
UIViewController *destinationViewController = self.destinationViewController;
UINavigationController *navigationController = sourceViewController.navigationController;
[navigationController.view addSubview:destinatiionViewController.view]
// Transformation start scale
destinationViewController.view.transform = CGAffineTransformMakeScale(0.05, 0.05);
// Store original centre point of the destination view
CGPoint originalCenter = destinationViewController.view.center;
// Set center to start point of the button
destinationViewController.view.center = self.originatingPoint;
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
// Grow!
destinationViewController.view.transform = CGAffineTransformMakeScale(1.0, 1.0);
destinationViewController.view.center = originalCenter;
}
completion:^(BOOL finished){
[destinationViewController.view removeFromSuperview]; // remove from temp super view
[navigationController presentViewController:destinationViewController animated:NO completion:NULL]; // present VC
}];
}
#end
Any ideas why this error occurs? What it means? And how to solve it?
I have the same error. The issue appears to be related to the fact that removeFromSuperview is called in the same run loop as the call to presentViewController:animated:completion for the same viewController/view.
One thing you can do to get rid of this warning is to present the view controller after a delay:
completion:^(BOOL finished)
{
destinationViewController.view removeFromSuperview];
[navigationController performSelector:#selector(presentViewController:) withObject:destinationViewController afterDelay:0];
}];
}
-(void)presentViewController:(UIViewController*)viewController
{
[[self presentViewController:viewController animated:NO completion:nil];
}
However, both this method and the one with the warning will sometimes have a flicker because there is one frame where the view has been removed from the superview, but not presented yet.
To get around this issue, you can create a snapshot view of the destination view controller, and add that to the view hierarchy in place of the actual destinationViewController's view, and then remove it AFTER the destinationViewController has been presented:
completion:^(BOOL finished)
{
UIView * screenshotView = [destinationViewController.view snapshotViewAfterScreenUpdates:NO];
screenshotView.frame = destinationViewController.view.frame;
[destinationViewController.view.superview insertSubview:screenshotView aboveSubview: destinationViewController.view];
[destinationViewController.view removeFromSuperview];
[self performSelector:#selector(presentViewControllerRemoveView:) withObject:#[destinationViewController, screenshotView] afterDelay:0];
}];
}
-(void)presentViewControllerRemoveView:(NSArray *)objects
{
UIViewController * viewControllerToPresent = objects[0];
UIView * viewToRemove = objects[1];
[self presentViewController:viewControllerToPresent
animated:NO
completion:
^{
[viewToRemove removeFromSuperview];
}];
}

iOS 7 navigationBar repositioning after dismissViewController

I have a controller embedded in navigation controller. Let's say that i have a button that repositions self.navigationController.navigationBar a bit. Then i do presentViewControllerAnimated with any controller (doesn't matter if it's nav or not) and after dismissing it navigation bar returns to it's original position (actually it is at its original position at dismiss animation start). In iOS 6 and earlier the bar would not be repositioned automatically. Any idea how can i prevent this repositioning in iOS 7?
OK, so I finally got it right.
First of all - Apple does not want us to change position of UINavigationBar. Therefore you should avoid it at all cost. In my case i got an app to fix which moved UINavigationBar to show slide-out menu. The proper solution to slide-out menu problem is to put UINavigationController inside - then you can slide whole UINavigationController with its content (whatever it is) and everything works fine. For some reason UINavigationController was outside in this app. So, i had to resort to a hack. Do not use this method if you have ANY option not to use it. It's a hack, it might break in further iOS versions and Apple would certainly not appreciate it.
First, explore new transitioning system in iOS7: http://www.doubleencore.com/2013/09/ios-7-custom-transitions/
Then, replace:
[self presentViewController:navigationController animated:YES completion:nil];
with
if([UIApplication iOS7]) /* or any other custom iOS7 detection method you implement */
{ /* we simulate old transition with nav bar slided out */
navigationController.transitioningDelegate = [OMModalTransitionDelegate new];
}
[self presentViewController:navigationController animated:YES completion:nil];
So, we need a transition delegate to simulate standard behaviour and do the trick as well.
#import "OMModalTransitionDelegate.h"
#import "OMAnimatedTransitioning.h"
#implementation OMModalTransitionDelegate
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
OMAnimatedTransitioning *transitioning = [OMAnimatedTransitioning new];
return transitioning;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
OMAnimatedTransitioning *transitioning = [OMAnimatedTransitioning new];
transitioning.reverse = YES;
return transitioning;
}
#end
And now the actual animation manager (you have to implement sharedBar in a category on UINavigationBar yourself):
static NSTimeInterval const DEAnimatedTransitionDuration = 0.4f;
static NSTimeInterval const DEAnimatedTransitionMarcoDuration = 0.15f;
#implementation OMAnimatedTransitioning
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *container = [transitionContext containerView];
UIView *superView = [UINavigationBar sharedBar].superview;
CGRect barFrame = [UINavigationBar sharedBar].frame;
if(self.reverse)
{ /* Trick - first, remove the bar from it's superview before animation starts */
[[UINavigationBar sharedBar] removeFromSuperview];
}
CGRect oldFrame = container.bounds;
if (self.reverse)
{
[container insertSubview:toViewController.view belowSubview:fromViewController.view];
}
else
{
toViewController.view.frame = oldFrame;
toViewController.view.transform = CGAffineTransformMakeTranslation(0, CGRectGetHeight(oldFrame));
[container addSubview:toViewController.view];
}
[UIView animateKeyframesWithDuration:DEAnimatedTransitionDuration delay:0 options:0 animations:^
{
if (self.reverse)
{
fromViewController.view.transform = CGAffineTransformMakeTranslation(0, CGRectGetHeight(oldFrame));
double delayInSeconds = 0.01; /* Trick - after an imperceivable delay - add it back - now it is immune to whatever Apple put there to move it */
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
{
[UINavigationBar sharedBar].frame = barFrame;
[superView addSubview:[UINavigationBar sharedBar]];
});
}
else
{
toViewController.view.transform = CGAffineTransformIdentity;
}
} completion:^(BOOL finished) {
[transitionContext completeTransition:finished];
}];
}
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return DEAnimatedTransitionDuration;
}
#end
In your custom navigation controller, add
- (void)viewWillLayoutSubviews {
//do your navigation bar layout
}
hope this can help you. Remind, above method only be supported ios >= 5.0.

Modifying UINavigationController corrupts navigation stack

I've got a standard UINavigation controller and push my screens with push and pop as normal. However, i have one screen that switches between two view controllers on a button press so the screen flips over top reveal the other viewcontroller and visa versa. You can switch between them at will.
Now, I want the back button to work as normal so I swap the top view controller to achieve this as follows:
-(IBAction)switchToSlowProductEntry:(id)sender
{
NSLog(#"Switching to slow product entry");
// Replace the top view with the findProductView
FindProductView *findProdView = [ProductViewInstances shared].findProductView;
NSMutableArray *views = [self.navigationController.viewControllers mutableCopy];
[views removeLastObject];
[views addObject:findProdView];
// if sender is nil then assume we started from the viewDidLoad so no animation
if(sender)
{
[UIView transitionWithView:self.navigationController.view duration:0.3 options:UIViewAnimationOptionTransitionFlipFromRight animations:^
{
[self.navigationController setViewControllers:views animated:NO];
}
completion:^(BOOL finished) {}];
}
else
[self.navigationController setViewControllers:views animated:NO];
NSLog(#"Views: %#", views);
[views release];
[ProductViewInstances shared].lastScreen = SlowProductEntryView;
}
-(IBAction)switchToQuickProductEntry:(id)sender
{
NSLog(#"Switching to fast product entry");
// Replace the top view with the findProductView
QuickOrderEntryView *quickProductView = [ProductViewInstances shared].quickProductView;
NSMutableArray *views = [self.navigationController.viewControllers mutableCopy];
[views removeLastObject];
[views addObject:quickProductView];
if(sender)
{
[UIView transitionWithView:self.navigationController.view duration:0.3 options:UIViewAnimationOptionTransitionFlipFromLeft animations:^
{
[self.navigationController setViewControllers:views animated:NO];
}
completion:^(BOOL finished) {}];
}
else
[self.navigationController setViewControllers:views animated:NO];
NSLog(#"Views: %#", views);
[views release];
[ProductViewInstances shared].lastScreen = QuickProductEntryView;
}
I have a similar piece of code for the other screen. I'm using the ProductViewInstances class to maintain the two view controllers as I do not want the classes to get unloaded as I'm maintaining stage on the screen.
When you want to move forward from these screens, I do the push as normal to a new screen. It work and I go back after reviewing the products I added. If I press back I get back to the above screen and everything seems normal. However, when I press my custom back button (I need to do processing if back pressed) I run into a problem.
The popViewController does nothing. Here is the code in the base class to manage the custom back button.
-(void) viewDidLoad
{
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(#"Back", nil)
style:UIBarButtonItemStyleBordered
target:self
action:#selector(myCustomBack)] autorelease];
if(![ProductViewInstances shared].findProductView)
{
[ProductViewInstances shared].findProductView = [[FindProductView alloc] init];
[ProductViewInstances shared].findProductView.customer = self.customer;
}
if(![ProductViewInstances shared].quickProductView)
{
[ProductViewInstances shared].quickProductView = [[QuickOrderEntryView alloc] init];
[ProductViewInstances shared].quickProductView.customer = self.customer;
}
}
-(void) goBack
{
if([[ProductViewInstances shared].quickProductView checkIfItemsPending])
{
// Pop up dialog
UIAlertView * alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Save Entries", nil)
message:NSLocalizedString(#"Your entries will be lost", nil)
delegate:self
cancelButtonTitle:NSLocalizedString(#"Cancel", nil)
otherButtonTitles:NSLocalizedString(#"Save", nil), nil];
[alert show];
[alert release];
}
else
{
// Remove rows from quick item entry screen
[[ProductViewInstances shared].quickProductView removeRowsFromtable];
if(didPressHome)
[self popToSpringBoard:YES];
else
[self.navigationController popViewControllerAnimated:YES];
}
}
So when I press back I have to check if the entries will be lost. The pop to SpringBoard pops back a couple of screens and basically calls the following:
NSArray *controllers = appDelegate.navigationController.viewControllers;
UIViewController *springboard = [controllers objectAtIndex:2];
[appDelegate.navigationController popToViewController:springboard animated:animated];
However, the popViewController animated call does nothing... Like as if it never happened.
Donie
#selector(myCustomBack) will not call goBack unless you left out some code.

Resources