How to get the coordinates of Hyperlink in UIWebview - objective-c

I'm loading the pdf (Having multiple Hyperlinks) document in UIWebview. I have to show UIPopover over hyperlinks dynamically.
I'm able to capture the coordinates of hyperlink using TapGesture Action method
- (void)tapAction:(UITapGestureRecognizer *)sender
{
self.point = [sender locationInView:self.myWebView];
}
And presenting the UIPopover over hyperlink by using below method
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL *rqstUrl = [request URL];
if (([[rqstUrl scheme] isEqualToString: #"https"] || [[rqstUrl scheme] isEqualToString: #"http"]) && (navigationType == UIWebViewNavigationTypeLinkClicked))
{
[self.myWebView stopLoading];
CGRect rect = CGRectMake(self.point.x,self.point.y-5, 5, 5);
UIPopoverController *popController = [[UIPopoverController alloc] initWithContentViewController:contentViewController];
popController.popoverContentSize = CGSizeMake(500, 200);
self.popController = popController;
self.popController.delegate =self;
UIPopoverArrowDirection direction = UIPopoverArrowDirectionUp|UIPopoverArrowDirectionDown;
self.popController.popoverLayoutMargins = UIEdgeInsetsMake(0, rect.origin.x, 1, 1);
[self.popController presentPopoverFromRect:rect inView:webView permittedArrowDirections:direction animated:YES];
}
return YES;
}
But the problem is if I tapped in two different locations within 1 or 2 seconds like First Tap is On Hyperlink and Second Tap is on "somewhere else in UIWebview", UIPopover is presenting at second tap location only not in hyperlink location.
I have to show UIPopover based on the Hyperlink position only, not in other location.How can I resolve this issue?

Use an overlay view
Replace your method to register the tap location by an overlay with a tap through. UITapGestureRecognizer has these limitations:
When a tap occurs outside of an hyperlink, it does registers its location, thanks to the UITapGestureRecognizer.
Unfortunately, a UIWebview Hyperlink taps take precedence over the gesture recognizer, and you never get the centroid. This is the real problem, causing the popover to appear misplaced.
UIPopoverController is deprecated in iOS 9.
"UIPopoverController is deprecated. Popovers are now implemented as UIViewController presentations. Use a modal presentation style of UIModalPresentationPopover and UIPopoverPresentationController."
tapAction and shouldStartLoadWithRequest are not coupled, and can occur independently of each other. Furthermore, they are basically mutually exclusive.
Use the overlay to register location in that view, and tap-though to the views underneath. If your overlay and web view have the same frame, you can use the tap position interchangeably. The overlay will guarantee tight coupling, and the rest of your method will work as designed.
class TapOverlayView: UIView {
var centroid:CGRect = CGRect.zero
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
centroid = CGRect(origin: point, size: CGSize(width: 1, height: 1))
return nil // tap through
}
}
Delegate
extension ViewController: UIWebViewDelegate {
public func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
let rqstUrl = request.url
if( rqstUrl!.scheme?.contains("http"))! && ( .linkClicked == navigationType) {
webView.stopLoading()
let contentViewController = storyboard!.instantiateViewController(withIdentifier: "popover")
contentViewController.modalPresentationStyle = .popover
contentViewController.preferredContentSize = CGSize(width: 200, height: 40)
if let popController = contentViewController.popoverPresentationController {
popController.permittedArrowDirections = .down
popController.sourceView = webView
popController.sourceRect = CGRect(origin: tap.centroid.origin, size: CGSize.zero)
present(contentViewController, animated: true, completion: nil)
}
}
return true
}
}
► Find this solution on GitHub and additional details on Swift Recipes.

Related

IOS8 how to move an active popover

I have developed an app for iOS7 and now trying to update it for iOS8.
Issue i have is the following:
The app screen orientation can be rotated and a few buttons in some cases move drastically. I have a few popovers that point to these buttons, so if a popover is open when screen rotates, button moves, i need the popover to also.
In iOS7 i did this by the following:
When screen rotated i updated the constraints
- (void) updateViewConstraints
{
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
{
self.Button.constant = (CGFloat)10;
}
else
{
self.Button.constant = (CGFloat)5;
}
[super updateViewConstraints];
}
I also move the popover
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
if(TempDisplayPopoverController == examplePopoverController)
{
[examplePopoverController presentPopoverFromRect:[self ExamplePopoverPosition] inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
}
I initially load the popover
- (void) LoadPopover{
examplePopover = [[examplep alloc] initWithNibName:#"exampleP" bundle:nil];
[examplePopover setDelegate:self];
examplePopoverController = [[UIPopoverController alloc] initWithContentViewController: examplePopover];
[examplePopoverController setDelegate:self];
examplePopoverController.popoverContentSize = examplePopover.view.frame.size;
TempDisplayPopoverController = examplePopoverController;
if ([examplePopoverController isPopoverVisible])
{
[examplePopoverController dismissPopoverAnimated:YES];
}
else
{
[examplePopoverController presentPopoverFromRect:[self ExamplePopoverPosition] inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
}
[self ExamplePopoverPosition] just returns button position.
This all worked fine, i was happy, iPad was happy and all behaved.
Now due to iOS8 i have to change a few bits.
self.interfaceOrientation is depreciated
[examplePopoverController presentPopoverFromRect:[self ExamplePopoverPosition] inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
in didRotateFromInterfaceOrientation throws an error
"Application tried to represent an active popover presentation: <UIPopoverPresentationController: 0x7bf59280>"
I've managed to rectify self.interfaceOrientation by
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self SetUpScreen:toInterfaceOrientation];
}
- (void) SetUpScreen:(UIInterfaceOrientation)toInterfaceOrientation{
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
self.Button.constant = (CGFloat)10;
}
else
{
self.Button.constant = (CGFloat)5;
}
[super updateViewConstraints];
}
but have no idea how to resolve the popover issue. I have tried
popoverController: willRepositionPopoverToRect: inView:
but just can't to seem to get it to work.
Can anyone advice
Thanks
In iOS 8 you can use -viewWillTransitionToSize:withTransitionCoordinator: to handle screen size (and orientation) changes:
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[_popover dismissPopoverAnimated:NO];
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
// Update your layout for the new size, if necessary.
// Compare size.width and size.height to see if you're in landscape or portrait.
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[_popover presentPopoverFromRect:[self popoverFrame]
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:NO];
}];
}
When you implement this method, the deprecated rotation methods like willAnimateRotationToInterfaceOrientation: will not be called when running on iOS 8.
When using popoverController:willRepositionPopoverToRect:inView:, when reassigning to the rect parameter, try using:
*rect = myNewRect;
and not:
rect = &presentingRect;
Also, make sure you have properly assigned the popover controller's delegate.
First, you don't need to dismiss and present the popover on rotation. UIPopoverPresentationController does that for you. You don't even need to update sourceView/sourceRect once they are set on creating the popover.
Now, the trick with animate(alongsideTransition: ((UIViewControllerTransitionCoordinatorContext) -> Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Void)? = nil) is that you should update your constraints in alongsideTransition closure, not in completion. This way you ensure that UIPopoverPresentationController has the updated sourceRect when restoring the popover at the end of rotation.
What might seem counter-intuitive is that inside alongsideTransition closure you already have your new layout that you derive your constraints calculation from.
Here's an example in Swift:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
if self.popover != nil {
// optionally scroll to popover source rect, if inside scroll view
let rect = ...
self.scrollView.scrollRectToVisible(rect, animated: false)
// update source rect constraints
myConstraint.constant = ...
myConstrainedView.setNeedsLayout()
myConstrainedView.layoutIfNeeded()
}
}, completion: nil)
}
Very interesting - I got this to work without updating the position manually. I don't know why this works though.
let buttonContainer = UIView(frame: CGRectMake(0, 0, 44, 44))
let button = UIButton(frame: CGRectMake(0, 0, 44, 44))
buttonContainer.addSubview(button)
view.addSubview(buttonContainer)
popover!.presentPopoverFromRect(button, inView: button.superview!, permittedArrowDirections: .Any, animated: true)
Put the button that the popover is presenting from inside a "container view". Then the popover will automatically adjust location upon orientation change.

Disabling NSView fade animation for NSView `setHidden:`

I am working on a project that has the concept of draggable controls, everything is working fine except that NSView seems to employ a fade in/out animation when calling setHidden:.
I have been able to work around the problem by changing the line session.animatesToStartingPositionsOnCancelOrFail = YES; to NO and implementing the image snapback myself with a custom animated NSWindow subclass. it looks great, but I know there must be an easier way.
I have tried:
using NSAnimationContext grouping with duration of 0 around the setHidden: calls
setting the view animations dictionary using various keys (alpha, hidden, isHidden) on the control and superview
overriding animationForKey: for both the control and its superview
I am not using CALayers and have even tried explicitly setting wantsLayer: to NO.
Does anybody know how to either disable this animation, or have a simpler solution then my animated NSWindow?
here is my stripped down altered code with the bare minimum to see what I'm talking about.
#implementation NSControl (DragControl)
- (NSDraggingSession*)beginDraggingSessionWithDraggingCell:(NSActionCell <NSDraggingSource> *)cell event:(NSEvent*) theEvent
{
NSImage* image = [self imageForCell:cell];
NSDraggingItem* di = [[NSDraggingItem alloc] initWithPasteboardWriter:image];
NSRect dragFrame = [self frameForCell:cell];
dragFrame.size = image.size;
[di setDraggingFrame:dragFrame contents:image];
NSArray* items = [NSArray arrayWithObject:di];
[self setHidden:YES];
return [self beginDraggingSessionWithItems:items event:theEvent source:cell];
}
- (NSRect)frameForCell:(NSCell*)cell
{
// override in multi-cell cubclasses!
return self.bounds;
}
- (NSImage*)imageForCell:(NSCell*)cell
{
return [self imageForCell:cell highlighted:[cell isHighlighted]];
}
- (NSImage*)imageForCell:(NSCell*)cell highlighted:(BOOL) highlight
{
// override in multicell cubclasses to just get an image of the dragged cell.
// for any single cell control we can just make sure that cell is the controls cell
if (cell == self.cell || cell == nil) { // nil signifies entire control
// basically a bitmap of the control
// NOTE: the cell is irrelevant when dealing with a single cell control
BOOL isHighlighted = [cell isHighlighted];
[cell setHighlighted:highlight];
NSRect cellFrame = [self frameForCell:cell];
// We COULD just draw the cell, to an NSImage, but button cells draw their content
// in a special way that would complicate that implementation (ex text alignment).
// subclasses that have multiple cells may wish to override this to only draw the cell
NSBitmapImageRep* rep = [self bitmapImageRepForCachingDisplayInRect:cellFrame];
NSImage* image = [[NSImage alloc] initWithSize:rep.size];
[self cacheDisplayInRect:cellFrame toBitmapImageRep:rep];
[image addRepresentation:rep];
// reset the original cell state
[cell setHighlighted:isHighlighted];
return image;
}
// cell doesnt belong to this control!
return nil;
}
#pragma mark NSDraggingDestination
- (void)draggingEnded:(id < NSDraggingInfo >)sender
{
[self setHidden:NO];
}
#end
#implementation NSActionCell (DragCell)
- (void)setControlView:(NSView *)view
{
// this is a bit of a hack, but the easiest way to make the control dragging work.
// force the control to accept image drags.
// the control will forward us the drag destination events via our DragControl category
[view registerForDraggedTypes:[NSImage imagePasteboardTypes]];
[super setControlView:view];
}
- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp
{
BOOL result = NO;
NSPoint currentPoint = theEvent.locationInWindow;
BOOL done = NO;
BOOL trackContinously = [self startTrackingAt:currentPoint inView:controlView];
BOOL mouseIsUp = NO;
NSEvent *event = nil;
while (!done)
{
NSPoint lastPoint = currentPoint;
event = [NSApp nextEventMatchingMask:(NSLeftMouseUpMask|NSLeftMouseDraggedMask)
untilDate:[NSDate distantFuture]
inMode:NSEventTrackingRunLoopMode
dequeue:YES];
if (event)
{
currentPoint = event.locationInWindow;
// Send continueTracking.../stopTracking...
if (trackContinously)
{
if (![self continueTracking:lastPoint
at:currentPoint
inView:controlView])
{
done = YES;
[self stopTracking:lastPoint
at:currentPoint
inView:controlView
mouseIsUp:mouseIsUp];
}
if (self.isContinuous)
{
[NSApp sendAction:self.action
to:self.target
from:controlView];
}
}
mouseIsUp = (event.type == NSLeftMouseUp);
done = done || mouseIsUp;
if (untilMouseUp)
{
result = mouseIsUp;
} else {
// Check if the mouse left our cell rect
result = NSPointInRect([controlView
convertPoint:currentPoint
fromView:nil], cellFrame);
if (!result)
done = YES;
}
if (done && result && ![self isContinuous])
[NSApp sendAction:self.action
to:self.target
from:controlView];
else {
done = YES;
result = YES;
// this bit-o-magic executes on either a drag event or immidiately following timer expiration
// this initiates the control drag event using NSDragging protocols
NSControl* cv = (NSControl*)self.controlView;
NSDraggingSession* session = [cv beginDraggingSessionWithDraggingCell:self
event:theEvent];
// Note that you will get an ugly flash effect when the image returns if this is set to yes
// you can work around it by setting NO and faking the release by animating an NSWindowSubclass with the image as the content
// create the window in the drag ended method for NSDragOperationNone
// there is [probably a better and easier way around this behavior by playing with view animation properties.
session.animatesToStartingPositionsOnCancelOrFail = YES;
}
}
}
return result;
}
#pragma mark - NSDraggingSource Methods
- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
{
switch(context) {
case NSDraggingContextOutsideApplication:
return NSDragOperationNone;
break;
case NSDraggingContextWithinApplication:
default:
return NSDragOperationPrivate;
break;
}
}
- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
{
// now tell the control view the drag ended so it can do any cleanup it needs
// this is somewhat hackish
[self.controlView draggingEnded:nil];
}
#end
There must be a layer enabled somewhere in your view hierarchy, otherwise there wouldn't be a fade animation. Here is my way of disabling such animations:
#interface NoAnimationImageView : NSImageView
#end
#implementation NoAnimationImageView
+ (id)defaultAnimationForKey: (NSString *)key
{
return nil;
}
#end
The solution you already tried by setting the view animations dictionary should work. But not for the keys you mention but for the following. Use it somewhere before the animation is triggered the first time. If you have to do it on the window or view or both, I don't know.
NSMutableDictionary *animations = [NSMutableDictionary dictionaryWithDictionary:[[theViewOrTheWindow animator] animations];
[animations setObject:[NSNull null] forKey: NSAnimationTriggerOrderIn];
[animations setObject:[NSNull null] forKey: NSAnimationTriggerOrderOut];
[[theViewOrTheWindow animator] setAnimations:animations];
Or also just remove the keys if they are there (might not be the case as they are implicit / default):
NSMutableDictionary *animations = [NSMutableDictionary dictionaryWithDictionary:[[theViewOrTheWindow animator] animations];
[animations removeObjectForKey:NSAnimationTriggerOrderIn];
[animations removeObjectForKey:NSAnimationTriggerOrderOut];
[[theViewOrTheWindow animator] setAnimations:animations];
Ok. I figured out that the animation I'm seeing is not the control, the superview, nor the control's window. It appears that animatesToStartingPositionsOnCancelOrFail causes NSDraggingSession to create a window (observed with QuartzDebug) and put the drag image in it and it is this window that animates back to the origin and fades out before the setHidden: call is executed (i.e. before the drag operation is concluded).
Unfortunately, the window that it creates is not an NSWindow so creating a category on NSWindow doesn't disable the fade animation.
Secondly, there is no public way that I know of to get a handle on the window, so I can't attempt directly manipulating the window instance.
It looks like maybe my workaround is the best way to do this, after all its not far from what AppKit does for you anyway.
If anybody knows how to get a handle on this window, or what class it is I would be interested to know.

How to re-size UITextView when keyboard shown with iOS 7

I have a view controller which contains a full-screen UITextView. When the keyboard is shown I would like to resize the text view so that it is not hidden under the keyboard.
This is a fairly standard approach with iOS, as described in this question:
How to resize UITextView on iOS when a keyboard appears?
However, with iOS 7, if the user taps on the text view in the bottom half of the screen, when the text view resizes, the cursor remains offscreen. The text view only scrolls to bring the cursor into view if when the user hits enter.
I read the docs which talk about this very topic. I translated it into Swift and it worked absolutely beautifully for me.
This is used for a full page UITextView like iMessage.
I am using iOS 8.2 and Swift on XCode 6.2 and here's my code. Just call this setupKeyboardNotifications from your viewDidLoad or other initialization method.
func setupKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWasShown:"), name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillBeHidden:"), name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWasShown(aNotification:NSNotification) {
let info = aNotification.userInfo
let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as NSValue
let kbSize = infoNSValue.CGRectValue().size
let contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0)
codeTextView.contentInset = contentInsets
codeTextView.scrollIndicatorInsets = contentInsets
}
func keyboardWillBeHidden(aNotification:NSNotification) {
let contentInsets = UIEdgeInsetsZero
codeTextView.contentInset = contentInsets
codeTextView.scrollIndicatorInsets = contentInsets
}
Also if you are having issues with the caret being in the right place when rotated check for the orientation change and scroll to the right position.
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
scrollToCaretInTextView(codeTextView, animated: true)
}
func scrollToCaretInTextView(textView:UITextView, animated:Bool) {
var rect = textView.caretRectForPosition(textView.selectedTextRange?.end)
rect.size.height += textView.textContainerInset.bottom
textView.scrollRectToVisible(rect, animated: animated)
}
Swift 3:
func configureKeyboardNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(aNotification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(aNotification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWasShown(aNotification:NSNotification) {
let info = aNotification.userInfo
let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as! NSValue
let kbSize = infoNSValue.cgRectValue.size
let contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0)
textView.contentInset = contentInsets
textView.scrollIndicatorInsets = contentInsets
}
func keyboardWillBeHidden(aNotification:NSNotification) {
let contentInsets = UIEdgeInsets.zero
textView.contentInset = contentInsets
textView.scrollIndicatorInsets = contentInsets
}
Swift 4 & 5:
func setupKeyboardNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_ :)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func keyboardWillShow(_ notification:NSNotification) {
let d = notification.userInfo!
var r = (d[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
r = self.textView.convert(r, from:nil)
self.textView.contentInset.bottom = r.size.height
self.textView.verticalScrollIndicatorInsets.bottom = r.size.height
}
#objc func keyboardWillHide(_ notification:NSNotification) {
let contentInsets = UIEdgeInsets.zero
self.textView.contentInset = contentInsets
self.textView.verticalScrollIndicatorInsets = contentInsets
}
With Auto Layout, it's much easier (provided you understand Auto Layout) to handle:
Instead of trying to identify and resize the affected views, you simply create a parent frame for all your view's contents. Then, if the kbd appears, you resize the frame, and if you've set up the constraints properly, the view will re-arrange all its child views nicely. No need to fiddle with lots of hard-to-read code for this.
In fact, in a similar question I found a link to this excellent tutorial about this technique.
Also, the other examples here that do use textViewDidBeginEditing instead of the UIKeyboardWillShowNotification have one big issue:
If the user has an external bluetooth keyboard attached then the control would still get pushed up even though no on-screen keyboard appears. That's not good.
So, to summarize:
Use Auto Layout
Use the UIKeyboardWillShowNotification notification,
not the TextEditField's events for deciding when to resize your
views.
Alternatively, check out LeoNatan's reply. That might even be a cleaner and simpler solution (I've not tried myself yet).
Do not resize the text view. Instead, set the contentInset and scrollIndicatorInsets bottom to the keyboard height.
See my answer here:
https://stackoverflow.com/a/18585788/983912
Edit
I made the following changes to your sample project:
- (void)textViewDidBeginEditing:(UITextView *)textView
{
_caretVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:#selector(_scrollCaretToVisible) userInfo:nil repeats:YES];
}
- (void)_scrollCaretToVisible
{
//This is where the cursor is at.
CGRect caretRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end];
if(CGRectEqualToRect(caretRect, _oldRect))
return;
_oldRect = caretRect;
//This is the visible rect of the textview.
CGRect visibleRect = self.textView.bounds;
visibleRect.size.height -= (self.textView.contentInset.top + self.textView.contentInset.bottom);
visibleRect.origin.y = self.textView.contentOffset.y;
//We will scroll only if the caret falls outside of the visible rect.
if(!CGRectContainsRect(visibleRect, caretRect))
{
CGPoint newOffset = self.textView.contentOffset;
newOffset.y = MAX((caretRect.origin.y + caretRect.size.height) - visibleRect.size.height + 5, 0);
[self.textView setContentOffset:newOffset animated:NO];
}
}
Removed setting old caret position at first, as well as disabled animation. Now seems to work well.
Whilst the answer given by #Divya lead me to the correct solution (so I awarded the bounty), it is not a terribly clear answer! Here it is in detail:
The standard approach to ensuring that a text view is not hidden by the on-screen keyboard is to update its frame when the keyboard is shown, as detailed in this question:
How to resize UITextView on iOS when a keyboard appears?
However, with iOS 7, if you change the text view frame within your handler for the UIKeyboardWillShowNotification notification, the cursor will remain off screen as described in this question.
The fix for this issue is to change the text view frame in response to the textViewDidBeginEditing delegate method instead:
#implementation ViewController {
CGSize _keyboardSize;
UITextView* textView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
textView = [[UITextView alloc] initWithFrame:CGRectInset(self.view.bounds, 20.0, 20.0)]; textView.delegate = self;
textView.returnKeyType = UIReturnKeyDone;
textView.backgroundColor = [UIColor greenColor];
textView.textColor = [UIColor blackColor];
[self.view addSubview:textView];
NSMutableString *textString = [NSMutableString new];
for (int i=0; i<100; i++) {
[textString appendString:#"cheese\rpizza\rchips\r"];
}
textView.text = textString;
}
- (void)textViewDidBeginEditing:(UITextView *)textView1 {
CGRect textViewFrame = CGRectInset(self.view.bounds, 20.0, 20.0);
textViewFrame.size.height -= 216;
textView.frame = textViewFrame;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CGRect textViewFrame = CGRectInset(self.view.bounds, 20.0, 20.0);
textView.frame = textViewFrame;
[textView endEditing:YES];
[super touchesBegan:touches withEvent:event];
}
#end
NOTE: unfortunately textViewDidBeginEdting fires before the UIKeyboardWillShowNotification notification, hence the need to hard-code the keyboard height.
Following on is working for me :
.h file
#interface ViewController : UIViewController <UITextViewDelegate> {
UITextView *textView ;
}
#property(nonatomic,strong)IBOutlet UITextView *textView;
#end
.m file
#implementation ViewController
#synthesize textView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CGRect textViewFrame = CGRectMake(20.0f, 20.0f, 280.0f, 424.0f);
//UITextView *textView = [[UITextView alloc] initWithFrame:textViewFrame];
textView.frame = textViewFrame;
textView.delegate = self;
textView.returnKeyType = UIReturnKeyDone;
textView.backgroundColor = [UIColor greenColor];
textView.textColor = [UIColor blackColor];
[self.view addSubview:textView];
}
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView{
NSLog(#"textViewShouldBeginEditing:");
return YES;
}
- (void)textViewDidBeginEditing:(UITextView *)textView1 {
NSLog(#"textViewDidBeginEditing:");
CGRect textViewFrame = CGRectMake(20.0f, 20.0f, 280.0f, 224.0f);
textView1.frame = textViewFrame;
}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView{
NSLog(#"textViewShouldEndEditing:");
return YES;
}
- (void)textViewDidEndEditing:(UITextView *)textView{
NSLog(#"textViewDidEndEditing:");
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
return YES;
}
- (void)textViewDidChange:(UITextView *)textView{
NSLog(#"textViewDidChange:");
}
- (void)textViewDidChangeSelection:(UITextView *)textView{
NSLog(#"textViewDidChangeSelection:");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"touchesBegan:withEvent:");
CGRect textViewFrame = CGRectMake(20.0f, 20.0f, 280.0f, 424.0f);
textView.frame = textViewFrame;
[self.view endEditing:YES];
[super touchesBegan:touches withEvent:event];
}
#end
i had done it and its work completely.
#define k_KEYBOARD_OFFSET 95.0
-(void)keyboardWillAppear {
// Move current view up / down with Animation
if (self.view.frame.origin.y >= 0)
{
[self moveViewUp:NO];
}
else if (self.view.frame.origin.y < 0)
{
[self moveViewUp:YES];
}
}
-(void)keyboardWillDisappear {
if (self.view.frame.origin.y >= 0)
{
[self moveViewUp:YES];
}
else if (self.view.frame.origin.y < 0)
{
[self moveViewUp:NO];
}
}
-(void)textFieldDidBeginEditing:(UITextField *)sender
{
//if ([sender isEqual:_txtPassword])
// {
//move the main view up, so the keyboard will not hide it.
if (self.view.frame.origin.y >= 0)
{
[self moveViewUp:YES];
}
//}
}
//Custom method to move the view up/down whenever the keyboard is appeared / disappeared
-(void)moveViewUp:(BOOL)bMovedUp
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.4]; // to slide the view up
CGRect rect = self.view.frame;
if (bMovedUp) {
// 1. move the origin of view up so that the text field will come above the keyboard
rect.origin.y -= k_KEYBOARD_OFFSET;
// 2. increase the height of the view to cover up the area behind the keyboard
rect.size.height += k_KEYBOARD_OFFSET;
} else {
// revert to normal state of the view.
rect.origin.y += k_KEYBOARD_OFFSET;
rect.size.height -= k_KEYBOARD_OFFSET;
}
self.view.frame = rect;
[UIView commitAnimations];
}
- (void)viewWillAppear:(BOOL)animated
{
// register keyboard notifications to appear / disappear the keyboard
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillAppear)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillDisappear)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
// unregister for keyboard notifications while moving to the other screen.
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
}
This is my solution, July 2015 using Swift 1.2 on Xcode 6.4 targeting iOS 7.1 - a combination of several approaches. Borrowed Johnston's keyboard handing Swift code. Its a bit of a hack, but its simple and it works.
I have a vanilla UITextView inside a single View.
I did not want to embed it inside a UIScrollView as per Apple's documentation. I just wanted the UITextView re-sized when software keyboard appeared, and resized to original when keyboard was dismissed.
These are the basic steps:
Set up keyboard notifications
Set up layout constraint in "Interface Builder" (TextView to bottom edge in my case)
Create an IBOutlet for this constraint in the relevant code file so you can adjust it programmatically
Use keyboard notifications to intercept events and get keyboard size
Programmatically adjust constraint IBOutlet using keyboard size to re-size TextView.
Put everything back when keyboard is dismissed.
So, onto the code.
I've set up constraint outlet at the top of the code file via the usual drag-drop in interface builder: #IBOutlet weak var myUITextViewBottomConstraint: NSLayoutConstraint!
I also set up a global variable where I can back up the state of affairs before the keyboard come up: var myUITextViewBottomConstraintBackup: CGFloat = 0
Implement keyboard notifications, call this function in viewDidLoad or any other startup/setup section:
func setupKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWasShown:"), name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillBeHidden:"), name: UIKeyboardWillHideNotification, object: nil)
}
Then these two functions will be called automatically when keyboard is shown/dismissed:
func keyboardWasShown(aNotification:NSNotification) {
let info = aNotification.userInfo
let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as! NSValue
let kbSize = infoNSValue.CGRectValue().size
let newHeight = kbSize.height
//backup old constraint size
myUITextViewBottomConstraintOld = myUITextViewBottomConstraint.constant
// I subtract 50 because otherwise it leaves a gap between keyboard and text view. I'm sure this could be improved on.
myUITextViewBottomConstraint.constant = newHeight - 50
func keyboardWillBeHidden(aNotification:NSNotification) {
//restore to whatever AutoLayout set it before you messed with it
myUITextViewBottomConstraint.constant = myUITextViewBottomConstraintOld
}
The code works, with a minor issue:
It's not responsive to the predictive text ribbon above the keyboard opening/closing. I.e. it will take the state of it into account when the keyboard is called up, but if you were to slide it up or down while keyboard is shown the constraint will not be adjusted. It is a separate event that needs to be handled. Its not enough of a functionality hit for me to bother with.
#Johnston found a good solution. Here's a variation using UIKeyboardWillChangeFrameNotification which correctly accounts for keyboard size changes (i.e. showing/hiding the QuickType bar). It also correctly handles the case where the text view is embedded in a navigation controller (i.e. where the contentInset isn't otherwise zero). It's also written in Swift 2.
override func viewDidLoad() {
:
NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardWillChangeFrameNotification, object: nil, queue: nil) { (notification) -> Void in
guard let userInfo = notification.userInfo,
let keyboardFrameEndValue = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue
else { return }
let windowCoordinatesKeyboardFrameEnd = keyboardFrameEndValue.CGRectValue() // window coordinates
let keyboardFrameEnd = self.view.convertRect(windowCoordinatesKeyboardFrameEnd, fromView: nil) // view coordinates
var inset = self.textView.contentInset
inset.bottom = CGRectGetMaxY(self.textView.frame) - CGRectGetMinY(keyboardFrameEnd) // bottom inset is the bottom of textView minus top of keyboard
self.textView.contentInset = inset
self.textView.scrollIndicatorInsets = inset
}
}

UIWebView without Copy/Paste when displaying PDF files

I have tried to disable Copy/Paste in UIWebView by using a category and overriding canPerformAction and returning NO for copy, cut and paste selectors.
It worked as expected when I loaded a webpage or all other document formats (e.g. docx, pptx, rtf, txt) but not when I loaded a PDF document into the UIWebView.
It seems like there is some different mechanism that handles PDF documents in UIWebView which handles/responds to Copy selector, and therefore I can not block it.
I also tried to disable user interaction for all the subviews of the UIWebView's UIScrollView, which worked fine for other document formats except PDF.
Can anyone help figuring out how to disable Copy in UIWebView for PDF documents as well?
OK, so I've been experiencing the same problem myself and seem to find a solution, even if it's partial.
What I do is use a UILongPressGestureRecognizer to disable long press gestures that can lead to copy/paste.
The code:
UILongPressGestureRecognizer* longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)]; // allocating the UILongPressGestureRecognizer
longPress.allowableMovement=100; // Making sure the allowable movement isn't too narrow
longPress.minimumPressDuration=0.3; // This is important - the duration must be long enough to allow taps but not longer than the period in which the scroll view opens the magnifying glass
longPress.delegate=self; // initialization stuff
longPress.delaysTouchesBegan=YES;
longPress.delaysTouchesEnded=YES;
longPress.cancelsTouchesInView=YES; // That's when we tell the gesture recognizer to block the gestures we want
[webView addGestureRecognizer:longPress]; // Add the gesture recognizer to the view and scroll view then release
[[webView scrollView] addGestureRecognizer:longPress];
[longPress release];
This solution worked for me:
METHOD 1 - Detect Custom Long Presses
A) Create a subclass of UILongPressGestureRecogniser.
B) Include the canBePreventedByGestureRecognizer: method in your subclass, like this:
Header:
#import <UIKit/UIKit.h>
#interface CustomLongPress : UILongPressGestureRecognizer
#end
Implementation:
#import "CustomLongPress.h"
#implementation CustomLongPress
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer*)preventedGestureRecognizer {
return NO;
}
#end
That's the only code you need in the subclass.
C) Open up the view containing your uiwebview/pdf reader. Include your subclass: #import "CustomLongPress.h" and then add the custom UILongPressGestureRecogniser to your UIWebView, like this:
- (void)viewDidLoad
{
[super viewDidLoad];
//Load UIWebView etc
//Add your custom gesture recogniser
CustomLongPress * longPress = [[CustomLongPress alloc] initWithTarget:self action:#selector(longPressDetected)];
[pdfWebView addGestureRecognizer:longPress];
}
D) Detect the long press and switch your UIWebView's userInteraction Off then back On:
-(void)longPressDetected {
NSLog(#"long press detected");
[pdfWebView setUserInteractionEnabled:NO];
[pdfWebView setUserInteractionEnabled:YES];
}
Apparently the reason this works is because the UIWebView captures long presses with its own gesture recognisers, to the exclusion of any additional gesture recongisers you've added. But subclassing your gesture recognisers and preventing their exclusion by returning "NO" to the canBePreventedByGestureRecognizer: method overrides the default behaviour.
Once you can detect the long presses on PDFs, switching the userInteraction Off then On again prevents the UIWebView from actioning its default behaviour, i.e. launching a "Copy/Define" UIMenu or, if long pressing over a link, launching a pop-up actionsheet with "Copy" and "Open" actions.
METHOD 2 - Catch UIMenu NSNotification
Alternatively, if you just want to block the "Copy/Define" UIMenu, (but not affect long presses), you can add this line (listening for UIMenuControllerDidShowMenuNotification) to your ViewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(menuShown) name:UIMenuControllerDidShowMenuNotification object:nil];
and then add this method, using the same userInteraction Off/On method as above:
-(void)menuShown {
NSLog(#"menu shown");
[pdfWebView setUserInteractionEnabled:NO];
[pdfWebView setUserInteractionEnabled:YES];
}
First method taken from: https://devforums.apple.com/thread/72521?start=25&tstart=0, and second method from somewhere on Stack, sorry forgotten where. Please include if you know.
Great answer Zubaba. I’m using a webView to display colored and bolded text and I had the same problem. I put your solution into a method and call it just after I initialize the webView. I don’t seem to need the delegate.
self.textView = [[UIWebView alloc] initWithFrame:textFrame];
[self longPress:self.textView];
- (void)longPress:(UIView *)webView {
UILongPressGestureRecognizer* longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress)]; // allocating the UILongPressGestureRecognizer
longPress.allowableMovement=100; // Making sure the allowable movement isn't too narrow
longPress.minimumPressDuration=0.3; // This is important - the duration must be long enough to allow taps but not longer than the period in which the scroll view opens the magnifying glass
longPress.delaysTouchesBegan=YES;
longPress.delaysTouchesEnded=YES;
longPress.cancelsTouchesInView=YES; // That's when we tell the gesture recognizer to block the gestures we want
[webView addGestureRecognizer:longPress]; // Add the gesture recognizer to the view and scroll view then release
[webView addGestureRecognizer:longPress];
}
- (void)handleLongPress {
}
In case someone needs Zubaba's answer in Swift 3;
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress))
longPress.allowableMovement = 100
longPress.minimumPressDuration = 0.3
longPress.delegate = self
longPress.delaysTouchesBegan = true
longPress.delaysTouchesEnded = true
longPress.cancelsTouchesInView = true
yourWebView.addGestureRecognizer(longPress)
yourWebView.scrollView.addGestureRecognizer(longPress)
func handleLongPress() {
// Show some alert to inform user or do nothing.
}
Here is a modification to Zubaba's answer in Swift 3 that ended up working for me to eliminate warning. I changed assignment longPress.delegate = self to longPress.delegate = self as? UIGestureRecognizerDelegate.
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress))
longPress.allowableMovement = 100
longPress.minimumPressDuration = 0.3
longPress.delegate = self as? UIGestureRecognizerDelegate
longPress.delaysTouchesBegan = true
longPress.delaysTouchesEnded = true
longPress.cancelsTouchesInView = true
webView.addGestureRecognizer(longPress)
webView.scrollView.addGestureRecognizer(longPress)
webView.loadRequest(request)
The UILongPressGestureRecognizer is located in the UIPDFPageView. To get access to this view look at the view hierarchy in the Debug menu, currently you can access this view like so once you load the pdf to the web view:
let pdfPageView = myWebView.scrollview?.subviews[0]?.subviews[0]
Then to remove the Long Press use this method while passing in the pdfPageView:
func removeLongPressFromView(view: UIView){
if let gestures = view.gestureRecognizers{
for gesture in gestures{
if gesture is UILongPressGestureRecognzier{
view.removeGestureRecognizer(gesture)
}
}
}
}
Looking for a Xamarin.iOS solution.
var longPressGestureRecognizer = new CustomLongPressGestureRecognizer ((UILongPressGestureRecognizer obj) =>
{
Console.WriteLine ("CustomLongPressGestureRecognizer action");
});
webView.AddGestureRecognizer (longPressGestureRecognizer);
The approach given by Zubaba might look like this:
public class ZubabaLongPressGestureRecognizer : UIKit.UILongPressGestureRecognizer
{
public ZubabaLongPressGestureRecognizer (Action<UILongPressGestureRecognizer> action)
: base (action)
{
AllowableMovement = 100;
MinimumPressDuration = 0.3;
DelaysTouchesBegan = true;
DelaysTouchesEnded = true;
CancelsTouchesInView = true;
}
}
The open/copy/cancel menu still shows the first time a link is long held per PDF page. After that first long press, however, it properly does not show up for that page. That this is PDF page dependent does not give me confidence that there is a solution available.
Johnny Rockex's solutions might look like this:
public class RockexLongPressGestureRecognizer : UIKit.UILongPressGestureRecognizer
{
public RockexLongPressGestureRecognizer(UIKit.UIWebView webView, Action<UILongPressGestureRecognizer> action) :
base(UserInteractionAction(webView) + action)
{
}
private static Action<UILongPressGestureRecognizer> UserInteractionAction(UIKit.UIWebView webView)
{
return (UILongPressGestureRecognizer obj) =>
{
webView.UserInteractionEnabled = false;
webView.UserInteractionEnabled = true;
};
}
public override bool CanPreventGestureRecognizer(UIGestureRecognizer preventedGestureRecognizer)
{
return false;
}
}
and
notificationToken1 = UIMenuController.Notifications.ObserveMenuFrameDidChange (Callback);
notificationToken2 = NSNotificationCenter.DefaultCenter.AddObserver(UIMenuController.DidShowMenuNotification, Callback);
I was not able to get either to do anything. Helpfully someone else has done better with a Xamarin.iOS fix
1.ios11 iphone6 Object-C Without Copy/Paste/lookUp/share
2.
viewDidLoad{
.......
[self setupExclude];
}
- (void)setupExclude{
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPG)];
longPress.minimumPressDuration = 0.2;
[self.webview addGestureRecognizer:longPress];
UITapGestureRecognizer *singleTapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:nil];
singleTapGesture.numberOfTapsRequired = 1;
singleTapGesture.numberOfTouchesRequired = 1;
[self.webview addGestureRecognizer:singleTapGesture];
UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(longPG)];
doubleTapGesture.numberOfTapsRequired = 2;
doubleTapGesture.numberOfTouchesRequired = 1;
[self.webview addGestureRecognizer:doubleTapGesture];
[singleTapGesture requireGestureRecognizerToFail:doubleTapGesture];
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender{
BOOL res = [super canPerformAction:action withSender:sender];
UIMenuController.sharedMenuController.menuVisible = NO;
self.webview.userInteractionEnabled = NO;
self.webview.userInteractionEnabled = YES;
return res;
}
- (void)longPG{
UIMenuController.sharedMenuController.menuVisible = NO;
self.webview.userInteractionEnabled = NO;
self.webview.userInteractionEnabled = YES;
}
3. Done!

detect long press on UINavigationItem's back button

I want to add functionality to my back buttons through my UINavigationController-based app where long-pressing the back button will pop to root. However, I can't figure out where to attach the gesture recognizer. Do I subclass UINavigationBar and try and detect if the long press is in the left button region?
I've heard of people adding similar functionality before. Anyone have any ideas?
I know this question is old, but I came up with a solution. Instead of trying to add the gesture recognizer to the button itself (which would be ideal), I added it to the self.navigationController.navigationBar and then in the action method, use the locationInView to see if I'm over the back button. I wasn't entirely sure about how to identify the back button precisely, so I'm clumsily just grabbing the the first subview with an x coordinate less than some arbitrary value, but it seems promising. If someone has a better way to identify the frame of the back button, let me know.
- (void)longPress:(UILongPressGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
// set a default rectangle in case we don't find the back button for some reason
CGRect rect = CGRectMake(0, 0, 100, 40);
// iterate through the subviews looking for something that looks like it might be the right location to be the back button
for (UIView *subview in self.navigationController.navigationBar.subviews)
{
if (subview.frame.origin.x < 30)
{
rect = subview.frame;
break;
}
}
// ok, let's get the point of the long press
CGPoint longPressPoint = [sender locationInView:self.navigationController.navigationBar];
// if the long press point in the rectangle then do whatever
if (CGRectContainsPoint(rect, longPressPoint))
[self doWhatever];
}
}
- (void)addLongPressGesture
{
if (NSClassFromString(#"UILongPressGestureRecognizer"))
{
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
[self.navigationController.navigationBar addGestureRecognizer:longPress];
[longPress release];
}
}
I believe UIGestureRecognizers can only be added to UIViews and subclasses of UIViews.
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIView_Class/UIView/UIView.html
The back button is a UIBarButtonItem that descends from NSObject. Therefore, you won't be able to attach a gesture recognizer to a standard back button using
UILongPressGestureRecognizer *longPressGesture =
[[[UILongPressGestureRecognizer alloc]
initWithTarget:self action:#selector(longPress:)] autorelease];
[self.navigationItem.backBarButtonItem addGestureRecognizer:longPressGesture];
You can however add a custom view to a UIBarButtonItem. A custom view could just as easily be a UIView, UIButton, UILabel, etc.
Example:
UIView *myTransparentGestureView = [[UIView alloc] initWithFrame:CGRectMake(0,0,40,30)];
[myTransparentGestureView addGestureRecognizer:longPressGesture];
[self.navigationItem.backBarButtonItem setCustomView:myTransparentGestureView];
// Or you could set it like this
// self.navigationItem.backBarButtonItem.customView = myTransparentGestureView;
[myTransparentGestureView release];
You have to be careful however, since setting properties on backBarButtonItem applies to the next view that you push. So if you have view A that pushes to view B and you want the gesture to be recognized when you tap back in view B. You must set it up in view A.
I followed a slightly different path, figured I'd share it. The above answers are fine, but really, if the long press is in the leading 1/3 of the nav bar, that's good enough for me:
- (void)longPress:(UILongPressGestureRecognizer *)gr
{
NSLog(#"longPress:");
UINavigationBar *navBar = [self navigationBar];
CGFloat height = navBar.bounds.size.height;
CGPoint pt = [gr locationOfTouch:0 inView:navBar];
//NSLog(#"PT=%# height=%f", NSStringFromCGPoint(pt), height);
if(CGRectContainsPoint(CGRectMake(0,0,100,height), pt)) {
[self popToViewController:self.viewControllers[0] animated:YES];
}
}
Here's my solution:
In appDelegate (the "owner" of the nav bar in my app), In applicationDidFinishLaunchingWithOptions:
Get the nav bar view and add the gesture recognizer to the whole view:
// Get the nav bar view
UINavigationBar *myNavBar = nil;
for (UIView *view in [self.window.rootViewController.view subviews]) {
if ([view isKindOfClass:[UINavigationBar class]]) {
NSLog(#"Found Nav Bar!!!");
myNavBar = (UINavigationBar *)view;
}
}
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:#selector(backButtonLongPress:)];
[myNavBar addGestureRecognizer:longPress];
NSLog(#"Gesture Recognizer Added.");
Then in appDelegate, in -(void) backButtonLongPress:(id) sender
Check to see if the gesture occurs within the frame of the back button:
if ([sender state] == UIGestureRecognizerStateBegan) {
// Get the nav bar view
UINavigationBar *myNavBar = nil;
for (UIView *view in [self.window.rootViewController.view subviews]) {
if ([view isKindOfClass:[UINavigationBar class]]) {
NSLog(#"Found Nav Bar!!!");
myNavBar = (UINavigationBar *)view;
}
}
// Get the back button view
UIView *backButtonView = nil;
for (UIView *view in [myNavBar subviews]) {
if ([[[view class] description] isEqualToString:#"UINavigationItemButtonView"]) {
backButtonView = view;
NSLog(#"Found It: %#", backButtonView);
NSLog(#"Back Button View Frame: %f, %f; %f, %f", backButtonView.frame.origin.x, backButtonView.frame.origin.y, backButtonView.frame.size.width, backButtonView.frame.size.height);
}
}
CGPoint longPressPoint = [sender locationInView:myNavBar];
NSLog(#"Touch is in back button: %#", CGRectContainsPoint(backButtonView.frame, longPressPoint) ? #"YES" : #"NO");
if (CGRectContainsPoint(backButtonView.frame, longPressPoint)) {
// Place your action here
}
// Do nothing if outside the back button frame
}