Warning: Attempt to present *** whose view is not in the window hierarchy - objective-c

I'm receiving this error when I am using an attached long press gesture to get a modal view to come up using the following code:
// Long press to go to settings for one
- (void)longPressOne:(UILongPressGestureRecognizer*)gesture {
[self performSegueWithIdentifier:#"buttonOne" sender:self];
}
// Long press to go to settings for two
- (void)longPressTwo:(UILongPressGestureRecognizer*)gesture {
[self performSegueWithIdentifier:#"buttonTwo" sender:self];
}
- (void)viewDidLoad {
// Add gesture to buttonOne
UILongPressGestureRecognizer *longPressOne = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressOne:)];
[self.buttonOne addGestureRecognizer:longPressOne];
// Add gesture to buttonTwo
UILongPressGestureRecognizer *longPressTwo = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressTwo:)];
[self.buttonTwo addGestureRecognizer:longPressTwo];
}
The modal segue is hitched up on the storyboard from the viewcontroller to the destination view. I know there are reports of this problem when there are multiple segues on the storyboard, but I just have the one as I can't create a segue from the button for a long press on Storyboard.
Any idea why this is happening?

I have fixed this by altering the code for handling the gestures, as below:
// Long press to go to settings for one
- (void)longPressOne:(UILongPressGestureRecognizer*)gesture {
if (gesture.state == UIGestureRecognizerStateBegan)
{
[self performSegueWithIdentifier:#"buttonOne" sender:self];
}
}
// Long press to go to settings for two
- (void)longPressTwo:(UILongPressGestureRecognizer*)gesture {
if (gesture.state == UIGestureRecognizerStateBegan)
{
[self performSegueWithIdentifier:#"buttonTwo" sender:self];
}
}
This seems to fix the problem.

Related

iOS Simulator Not Recognizing Gestures

I am adding a UISwipeGestureRecognizer and a UITapGestureRecognizer to a view in a view controller's viewDidLoad method.
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addGestureRecognizer:[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(cardSwipe:)]];
[self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(cardTap:)]];
}
- (void)cardSwipe:(UISwipeGestureRecognizer *)sender {
//get the card. set faceUp to false.
CGPoint location = [sender locationInView:sender.view];
NSIndexPath *cellIndex = [self.cardCollectionView indexPathForItemAtPoint:location];
if(cellIndex){
UICollectionViewCell *cell = [self collectionView:self.cardCollectionView cellForItemAtIndexPath:cellIndex];
if(cell && [cell isKindOfClass:[CardCollectionViewCell class]]){
[[((CardCollectionViewCell *)cell) cardView] handleCardSwipe];
}
}
}
- (void)cardTap:(UITapGestureRecognizer *)sender {
//get the card. set faceUp to false.
CGPoint location = [sender locationInView:sender.view];
NSIndexPath *cellIndex = [self.cardCollectionView indexPathForItemAtPoint:location];
if(cellIndex){
UICollectionViewCell *cell = [self collectionView:self.cardCollectionView cellForItemAtIndexPath:cellIndex];
if(cell && [cell isKindOfClass:[CardCollectionViewCell class]]){
[[((CardCollectionViewCell *)cell) cardView] handleCardSwipe];
}
}
}
In case this is relevant: The view contains a UICollectionView.
The taps and swipes are not getting recognized. Is there something obvious that I am missing?
Thanks.
Restarting the simulator worked for me.
Turns out the view was not responding to any gestures - scrolling, taps on buttons or the swipe actions. I deleted generated folders from ~/Library/Application Support/iPhone Simulator / 6.1/Applications and ~/Library/Developer/Xcode/DerivedData, reset the simulator settings (from iOS Simulator > Reset Contents and Settings), did a clean in xcode (Product > Clean) and ran the app again. The gestures are now recognized. I am not sure which of the above fixed the problem...it is possible that simply resetting the simulator's contents and settings would have been enough.
You just need to select Show Device Bezels:
Goto simulator > Window > Enable Show Device Bezels
Enjoy your swipe to back gesture.
add this method to your viewcontroller so your UICollectionView doesn't block other gestures
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return true;
}
In case you have already enabled Device Bezels from Window panel but still can't use swipe to go back gesture. Please refer to this answer.
All you need to do is restart the simulator and try to swipe from the real edge of the simulator.
First you Need To Add UITapGestureRecognizer Delegate method To .h
#interface ViewController : UIViewController<UIGestureRecognizerDelegate>
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doubleTapImgView:)];
doubleTap.numberOfTapsRequired = 2;
doubleTap.delegate = self;
- (void)doubleTapImgView:(UITapGestureRecognizer *)gesture
{
//Do What you want Here
}

Pop up UIViewController with an IBAction UIViewController

Is it possible to pop up an UIViewController (xib file) like UIPopOverControl in iPad ?
I have a separate NIB file which is linked to an UIViewController. I want to popup that NIB file along with the button pressed with a customised size (200,200).
Is this possible?
I am trying to get something like this on the iPhone - http://www.freeimagehosting.net/c219p
You can also use one of these custom made clases to show a popup:
https://github.com/sonsongithub/PopupView
https://github.com/werner77/WEPopover
https://github.com/50pixels/FPPopover
Example with FPPopover:
//the view controller you want to present as popover
YourViewController *controller = [[YourViewController alloc] init];
//our popover
FPPopoverController *popover = [[FPPopoverController alloc] initWithViewController:controller];
//the popover will be presented from the okButton view
[popover presentPopoverFromView:okButton];
//release if you arent using ARC
[controller release];
yes it is. load Your pOpOver controller lazily at the point when it is needed. add its view as a subview (you could animate the addition). make its frame size what You need and add the image You have shown as a background subview of the pOpOver controller along with other controls You want in the pop up.
good luck
UPDATE:
alright, ii will show You how ii do this in my app Lucid Reality Check (deployment target iOS4.3).
one can use a UIPopoverController to present another controllers view. what ii do first is to make sure ii always know the current orientation of the device, so ii can reposition the popup on rotation (maybe this works by itself on iOS6?). so in my base controller (from where ii want to show a popup) ii have an instance variable like this:
UIInterfaceOrientation toOrientation;
and also:
UIPopoverController *popover;
UIButton *popover_from_button;
BOOL representPopover;
popover will be reused for all popups, and popover_from_button will hold the button from which the popup is initiated.
then the next code comes into the base controller:
- (void)popoverWillRotate {
if ([popover isPopoverVisible]) {
[self dismissPopover];
representPopover = YES;
}
}
- (void)popoverDidRotate {
if (popover && representPopover) {
representPopover = NO;
[self representPopover];
}
}
these two methods have to be called every time the device is rotated, like this:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//DLOG(#"willRotateTo %i", toInterfaceOrientation);
toOrientation = toInterfaceOrientation;
if ([Kriya isPad ]) {
[self popoverWillRotate];
}
}
as one can see, first the orientation is captured then popoverWillRotate is called. this would hide the popover during the orientation animation. and after rotating, the popover must be redisplayed like this:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
//DLOG(#"didRotateFrom %i", fromInterfaceOrientation);
//[self layout:toOrientation]; //do some layout if You need
if ([Kriya isPad]) {
[self popoverDidRotate];
}
}
- (void)layout:(UIInterfaceOrientation)toInterfaceOrientation {
//one can do view layout here, and call other controllers to do their layout too
}
now that the orientation changes are worked out, the code for presenting the popover arrives here:
#pragma mark Popovers
- (void)presentPopoverWith:(id)controller fromButton:(UIButton*)button {
if (popover)
[popover release];
if (popover_from_button)
[popover_from_button release];
popover_from_button = [button retain];
popover = [[UIPopoverController alloc] initWithContentViewController:controller];
[popover setDelegate:self];
[self representPopover];
}
- (void)representPopover{
if (popover) {
UIPopoverArrowDirection arrowDirection = UIPopoverArrowDirectionAny;
UIViewController *vc = (UIViewController*)[popover contentViewController];
CGSize contentSize = [vc contentSizeForViewInPopover];
if (contentSize.width > 0 && contentSize.height > 0) {
[popover setPopoverContentSize:contentSize animated:NO];
}
//DLOG(#"representPopover rect:%#", [Kriya printRect:popover_from_button.frame]);
[popover presentPopoverFromRect:CGRectOffset(popover_from_button.frame, 0, popover_from_button.frame.size.height + 7.0) inView:self.view permittedArrowDirections:arrowDirection animated:YES];
}
}
- (void)dismissPopover {
if (popover) {
[popover dismissPopoverAnimated:YES];
}
}
finally, if one wants to be notified when the popover is dismissed, the base controller must implement a delegate method:
#pragma mark UIPopoverControllerDelegate
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
//do something important here like drink some water
}
and don't forget to make the base controller a UIPopoverControllerDelegate in its header.
a use case for this way of doing popups would then look like this:
- (void)takeImage {
UIImagePickerController *picker = [[[UIImagePickerController alloc] init] autorelease];
[picker setDelegate:self];
[picker setAllowsEditing:NO];
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
[picker setSourceType:UIImagePickerControllerSourceTypeCamera];
if ([Kriya isPad]) {
[self presentPopoverWith:picker fromButton:backgroundImageButton];
} else {
//modals on iPhone/iPod
//DLOG(#"takeImage addSubview picker");
[self presentModalViewController:picker animated:YES];
}
} else {
//DLOG(#"no camera");
}
}
this would use an image picker as the content for the popup, but one can use any controller with a valid view. so just do this:
[self presentPopoverWith:popupsContentController fromButton:tappedButton];
one should not have any missing information, :), the method [Kriya isPad] is just this:
+ (BOOL)isPad {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 30200
// iPad capable OS
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
//this is an iPad
return YES;
}else {
//this is an iPod/iPhone
return NO;
}
#else
//can not pissible be iPad
return NO;
#endif
}
ENJOY!

UIMenuController not responding to first selection, only second

I have a view with a long press gesture recognizer in it:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressDetected:)];
[self addGestureRecognizer:longPress];
[longPress release];
}
return self;
}
When the long press is detected, I want to show a UIMenuViewController above the view with a single action in it, and when that menu item is tapped I want to execute a block:
- (void)longPressDetected:(UILongPressGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
[self becomeFirstResponder];
UIMenuController *menuController = [UIMenuController sharedMenuController];
UIMenuItem *actionItem = [[UIMenuItem alloc] initWithTitle:#"Action" action:#selector(someActionSelector)];
[menuController setMenuItems:[NSArray arrayWithObject:actionItem]];
[actionItem release];
[menuController setTargetRect:self.frame inView:self.superview];
[menuController setMenuVisible:YES animated:YES];
}
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if (action == #selector(copy:) || action == #selector(cut:) || action == #selector(delete:) ||
action == #selector(paste:) || action == #selector(select:) || action == #selector(selectAll:)) {
return NO;
}
else if (action == #selector(someActionSelector)) {
return YES;
}
else {
return [super canPerformAction:action withSender:sender];
}
}
- (void)someActionSelector {
if (self.actionBlock) {
self.actionBlock();
}
}
Problem is, this only works after the second long-press and tap combo. The first time I long-press on the view I see the menu, but tapping the menu does nothing. The second time I see the menu again, I tap it, then the block is executed.
Debugger shows that a breakpoint in someActionSelector is only reached on the second tap. Any idea why this is?
I figured it out. The view listening for a long press is contained inside a view that repositions some subviews when its frame is changed (by overriding setFrame:, which seems like a bad idea but I couldn't think of another way). So when the long press happened it triggered a layoutSubviews in the parent of the parent of the listening view, which set the frame of the parent of the listening view, which repositioned the listening view, which appears to break either the responder chain or deactivate the menu. The solution was to add a condition inside the overridden setFrame: to only trigger the layout if the frame actually changes, which it doesn't on the long press. I'm sure there's a better alternative for listening to frame changes that would avoid this problem entirely—feel free to suggest them in comments.

Trigger setEditing: animated: without using an edit button

I have a UITableView with some custom cells in it. In these custom cells I defined a UILongPressGestureRecognizer that triggers the edit mode of this table. So when someone presses and holds a cell for like 1.5 sec, the table goes into edit mode.
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(startEditMode:)];
Which triggers:
- (void)startEditMode:(UISwipeGestureRecognizer *)recognizer {
if (self.allowEdit) {
UITableView *table = (UITableView *)self.superview;
[table setEditing:YES animated:YES];
}
}
But what I want to do is detect when the table goes into edit mode because I need to show/hide some additional buttons in this case. But for some reason in my viewcontroller this is never executed:
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
NSLog(#"SET EDITING");
[super setEditing:editing animated:animated];
}
Any suggestion why? Is this just being called when using a proper Edit Button as provided by default in the UINavigationController?
Or how can I detect when my UITableView goes into Edit Mode?
You're sending the message (setEditing) to the table view, you should be sending it to the view controller (presumably a UITableViewController subclass?). It will then take care of the table view for you.
Ok so in case someone else walks into this thread with the same problem, I will show you how I solved this.
In my custom UITableViewCell I have this method now:
- (void)startEditMode:(UISwipeGestureRecognizer *)recognizer {
if (self.allowEdit) {
UITableView *table = (UITableView *)self.superview;
UITableViewController *control = (UITableViewController *)table.dataSource;
[control setEditing:YES animated:YES];
}
}

UIGestureRecognizer blocks subview for handling touch events

I'm trying to figure out how this is done the right way. I've tried to depict the situation:
I'm adding a UITableView as a subview of a UIView. The UIView responds to a tap- and pinchGestureRecognizer, but when doing so, the tableview stops reacting to those two gestures (it still reacts to swipes).
I've made it work with the following code, but it's obviously not a nice solution and I'm sure there is a better way. This is put in the UIView (the superview):
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if([super hitTest:point withEvent:event] == self) {
for (id gesture in self.gestureRecognizers) {
[gesture setEnabled:YES];
}
return self;
}
for (id gesture in self.gestureRecognizers) {
[gesture setEnabled:NO];
}
return [self.subviews lastObject];
}
I had a very similar problem and found my solution in this SO question. In summary, set yourself as the delegate for your UIGestureRecognizer and then check the targeted view before allowing your recognizer to process the touch. The relevant delegate method is:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch
The blocking of touch events to subviews is the default behaviour. You can change this behaviour:
UITapGestureRecognizer *r = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(agentPickerTapped:)];
r.cancelsTouchesInView = NO;
[agentPicker addGestureRecognizer:r];
I was displaying a dropdown subview that had its own tableview. As a result, the touch.view would sometimes return classes like UITableViewCell. I had to step through the superclass(es) to ensure it was the subclass I thought it was:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
UIView *view = touch.view;
while (view.class != UIView.class) {
// Check if superclass is of type dropdown
if (view.class == dropDown.class) { // dropDown is an ivar; replace with your own
NSLog(#"Is of type dropdown; returning NO");
return NO;
} else {
view = view.superview;
}
}
return YES;
}
Building on #Pin Shih Wang answer. We ignore all taps other than those on the view containing the tap gesture recognizer. All taps are forwarded to the view hierarchy as normal as we've set tapGestureRecognizer.cancelsTouchesInView = false. Here is the code in Swift3/4:
func ensureBackgroundTapDismissesKeyboard() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
tapGestureRecognizer.cancelsTouchesInView = false
self.view.addGestureRecognizer(tapGestureRecognizer)
}
#objc func handleTap(recognizer: UIGestureRecognizer) {
let location = recognizer.location(in: self.view)
let hitTestView = self.view.hitTest(location, with: UIEvent())
if hitTestView?.gestureRecognizers?.contains(recognizer) == .some(true) {
// I dismiss the keyboard on a tap on the scroll view
// REPLACE with own logic
self.view.endEditing(true)
}
}
One possibility is to subclass your gesture recognizer (if you haven't already) and override -touchesBegan:withEvent: such that it determines whether each touch began in an excluded subview and calls -ignoreTouch:forEvent: for that touch if it did.
Obviously, you'll also need to add a property to keep track of the excluded subview, or perhaps better, an array of excluded subviews.
It is possible to do without inherit any class.
you can check gestureRecognizers in gesture's callback selector
if view.gestureRecognizers not contains your gestureRecognizer,just ignore it
for example
- (void)viewDidLoad
{
UITapGestureRecognizer *singleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
singleTapGesture.numberOfTapsRequired = 1;
}
check view.gestureRecognizers here
- (void)handleSingleTap:(UIGestureRecognizer *)gestureRecognizer
{
UIEvent *event = [[UIEvent alloc] init];
CGPoint location = [gestureRecognizer locationInView:self.view];
//check actually view you hit via hitTest
UIView *view = [self.view hitTest:location withEvent:event];
if ([view.gestureRecognizers containsObject:gestureRecognizer]) {
//your UIView
//do something
}
else {
//your UITableView or some thing else...
//ignore
}
}
I created a UIGestureRecognizer subclass designed for blocking all gesture recognizers attached to a superviews of a specific view.
It's part of my WEPopover project. You can find it here.
implement a delegate for all the recognizers of the parentView and put the gestureRecognizer method in the delegate that is responsible for simultaneous triggering of recognizers:
func gestureRecognizer(UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer:UIGestureRecognizer) -> Bool {
if (otherGestureRecognizer.view.isDescendantOfView(gestureRecognizer.view)) {
return true
} else {
return false
}
}
U can use the fail methods if u want to make the children be triggered but not the parent recognizers:
https://developer.apple.com/reference/uikit/uigesturerecognizerdelegate
I was also doing a popover and this is how I did it
func didTap(sender: UITapGestureRecognizer) {
let tapLocation = sender.locationInView(tableView)
if let _ = tableView.indexPathForRowAtPoint(tapLocation) {
sender.cancelsTouchesInView = false
}
else {
delegate?.menuDimissed()
}
}
You can turn it off and on.... in my code i did something like this as i needed to turn it off when the keyboard was not showing, you can apply it to your situation:
call this is viewdidload etc:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(notifyShowKeyboard:) name:UIKeyboardDidShowNotification object:nil];
[center addObserver:self selector:#selector(notifyHideKeyboard:) name:UIKeyboardWillHideNotification object:nil];
then create the two methods:
-(void) notifyShowKeyboard:(NSNotification *)inNotification
{
tap.enabled=true; // turn the gesture on
}
-(void) notifyHideKeyboard:(NSNotification *)inNotification
{
tap.enabled=false; //turn the gesture off so it wont consume the touch event
}
What this does is disables the tap. I had to turn tap into a instance variable and release it in dealloc though.