SwiftUI print a view - swift

I have an old iOS app that uses UIKit and Objective C which I am currecntly porting to SwiftUI and Swift. Everything was going great and I am loving Swift and SwiftUI. The app is pretty much done but the app relies on the user being able to print and/or save the main view as a PDF. I just can't figure out how to access a view in swiftui in order to convert it to a PDF. Here is my existing/working objective-c code.
- (IBAction)actionPrint:(id)sender {
// CREATE CLEAR BACKGROUND
[legMain setBackgroundColor:[UIColor clearColor]];
// SCROLL TO BASE POSITION
[legMain scrollRectToVisible:CGRectMake(1, 1, 1, 1) animated:NO];
// RESET ZOOM
SnapPanel *myObject = [self fGetObject];
myObject.zoom = [NSNumber numberWithDouble:1.0];
[self fSave];
// RECORD FRAME SIZE AND SET TO CONTENT SIZE
double dWidth = legMain.frame.size.width;
double dHeight = legMain.frame.size.height;
[legMain setFrame:CGRectMake(legMain.frame.origin.x, legMain.frame.origin.y, legMain.contentSize.width, legMain.contentSize.height)];
// GET VIEW AS NSDATA FOR PDF
NSMutableData *pdfData = [NSMutableData data];
CGRect pageSize = CGRectMake(0.0, 0.0, 8.5 * 72.0, 11.0 * 72.0);
UIGraphicsBeginPDFContextToData(pdfData, pageSize, nil);
CGContextRef pdfContext = UIGraphicsGetCurrentContext();
// CREATE A SINGLE PAGE PDF
UIGraphicsBeginPDFPage();
[legMain.layer renderInContext:pdfContext];
UIGraphicsEndPDFContext();
// CREATE PRINT CONTROLLER
UIPrintInteractionController *pc = [UIPrintInteractionController sharedPrintController];
void (^completionHandler)(UIPrintInteractionController *, BOOL, NSError *) =
^(UIPrintInteractionController *pic, BOOL completed, NSError *error) {
if (!completed && error){
NSLog(#"Print error: %#", error);
}
};
// SETUP PRINT CONTROLLER
[pc setShowsNumberOfCopies:YES];
[pc setShowsPageRange:YES];
[pc setShowsPaperSelectionForLoadedPapers:YES];
pc.printingItem = pdfData;
// DISPLAY CONTROLLER DIALOG
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[pc presentFromRect:texName.frame inView:viewMain.superview
animated:YES completionHandler:completionHandler];
} else {
[pc presentAnimated:YES completionHandler:completionHandler];
}
// RESET BACKGROUND COLOUR AND FRAME SIZE
[legMain setBackgroundColor:[UIColor colorWithRed:.95 green:.95 blue:.95 alpha:1.0]];
[legMain setFrame:CGRectMake(legMain.frame.origin.x, legMain.frame.origin.y, dWidth, dHeight)];
}
legMain is the view that I am adjusting and eventually converting to a PDF. I have managed to port most of this code untill I reach line 26 where I need to render the view. I don't even know where to begin to get an instance of my view.

Having access to hosting window as described in How to access own window within SwiftUI view? you can get main UIView from any of your SwiftUI view as
let mainView = hostingWindow?.rootViewController.view
but for printing I would probably use dedicated view as below (to avoid affecting UI view)
if let hostingController = hostingWindow?.rootController as UIHostingController {
let printingView = UIHostingController(rootView: hostingController.rootView).view
// ... do anything with printingView here, because it will have
// a copy of SwiftUI rootView
}
Update: to the details in comment (hosting window is not needed in this case)
VStack {
PrintableView(arg: value) // << need to be made undependable, so all
// parameters should be passed via arguments
}
then on Print action it could be like
Button("Print") {
let printingView = UIHostingController(rootView: PrintableView(arg: value)).view
// do anything printing related with `printingView` here
}

Related

Print from UIWebView - Swift

I need to view a form on a webpage, through a UIWebView on an iPad app. After the user submits the form, I need to send a print job from the form on the webpage through to an AirPrint enabled printer on the same wifi network as the iPad. I have access to do anything I need to on the website. I have read that the UIWebView cannot recognise the window.print() JavaScript command.
What can I do to get the iPad app to receive a print job and print via AirPrint?
I have seen that other people on this website have asked similar questions, but none of them have been answered with much detail.
There is a sample project by Apple on printing UIwebView content.
Print Web View Content
- (IBAction)printWebPage:(id)sender
{
UIPrintInteractionController *controller = [UIPrintInteractionController sharedPrintController];
if(!controller){
NSLog(#"Couldn't get shared UIPrintInteractionController!");
return;
}
UIPrintInteractionCompletionHandler completionHandler =
^(UIPrintInteractionController *printController, BOOL completed, NSError *error) {
if(!completed && error){
NSLog(#"FAILED! due to error in domain %# with error code %u", error.domain, error.code);
}
};
// Obtain a printInfo so that we can set our printing defaults.
UIPrintInfo *printInfo = [UIPrintInfo printInfo];
// This application produces General content that contains color.
printInfo.outputType = UIPrintInfoOutputGeneral;
// We'll use the URL as the job name.
printInfo.jobName = [self.urlField text];
// Set duplex so that it is available if the printer supports it. We are
// performing portrait printing so we want to duplex along the long edge.
printInfo.duplex = UIPrintInfoDuplexLongEdge;
// Use this printInfo for this print job.
controller.printInfo = printInfo;
// Be sure the page range controls are present for documents of > 1 page.
controller.showsPageRange = YES;
// This code uses a custom UIPrintPageRenderer so that it can draw a header and footer.
APLPrintPageRenderer *myRenderer = [[APLPrintPageRenderer alloc] init];
// The APLPrintPageRenderer class provides a jobtitle that it will label each page with.
myRenderer.jobTitle = printInfo.jobName;
// To draw the content of each page, a UIViewPrintFormatter is used.
UIViewPrintFormatter *viewFormatter = [self.myWebView viewPrintFormatter];
#if SIMPLE_LAYOUT
/*
For the simple layout we simply set the header and footer height to the height of the
text box containing the text content, plus some padding.
To do a layout that takes into account the paper size, we need to do that
at a point where we know that size. The numberOfPages method of the UIPrintPageRenderer
gets the paper size and can perform any calculations related to deciding header and
footer size based on the paper size. We'll do that when we aren't doing the simple
layout.
*/
UIFont *font = [UIFont fontWithName:#"Helvetica" size:HEADER_FOOTER_TEXT_HEIGHT];
CGSize titleSize = [myRenderer.jobTitle sizeWithFont:font];
myRenderer.headerHeight = myRenderer.footerHeight = titleSize.height + HEADER_FOOTER_MARGIN_PADDING;
#endif
[myRenderer addPrintFormatter:viewFormatter startingAtPageAtIndex:0];
// Set our custom renderer as the printPageRenderer for the print job.
controller.printPageRenderer = myRenderer;
/*
The method we use to present the printing UI depends on the type of UI idiom that is currently executing. Once we invoke one of these methods to present the printing UI, our application's direct involvement in printing is complete. Our custom printPageRenderer will have its methods invoked at the appropriate time by UIKit.
*/
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[controller presentFromBarButtonItem:self.printButton animated:YES completionHandler:completionHandler]; // iPad
}
else {
[controller presentAnimated:YES completionHandler:completionHandler]; // iPhone
}
}

Photo Size With UIPrintInteractionController

I have one problem to print photo using AirPrint. I printed 4 * 6 inch image but printed image size is too large! How can I resolve this problem.
Can I specify paper size and photo programmatically?
Here is screen shot url.
https://www.dropbox.com/s/1f6wa0waao56zqk/IMG_0532.jpg
` here is my code
-(void)printPhotoWithImage:(UIImage *)image
{
NSData *myData = UIImageJPEGRepresentation(image, 1.f);
UIPrintInteractionController *pic = [UIPrintInteractionController sharedPrintController];
if (pic && [UIPrintInteractionController canPrintData:myData]) {
pic.delegate = self;
UIPrintInfo *pinfo = [UIPrintInfo printInfo];
pinfo.outputType = UIPrintInfoOutputPhoto;
pinfo.jobName = #"My Photo";
pinfo.duplex = UIPrintInfoDuplexLongEdge;
pic.printInfo = pinfo;
pic.showsPageRange = YES;
pic.printingItem = myData;
pic.printFormatter = format;
[format release];
void(^completionHandler)(UIPrintInteractionController *, BOOL, NSError *) = ^(UIPrintInteractionController *print, BOOL completed, NSError *error) {
[self resignFirstResponder];
if (!completed && error) {
NSLog(#"--- print error! ---");
}
};
[pic presentFromRect:CGRectMake((self.view.bounds.size.width - 64) + 27, (self.view.bounds.size.height - 16) + 55, 0, 0) inView:self.view animated:YES completionHandler:completionHandler];
}
}
- (UIPrintPaper *)printInteractionController:(UIPrintInteractionController *)printInteractionController choosePaper:(NSArray *)paperList
{
CGSize pageSize = CGSizeMake(6 * 72, 4 * 72);
return [UIPrintPaper bestPaperForPageSize:pageSize withPapersFromArray:paperList];
}
Just this is my code. should I use UIPrintPageRenderer property to give draw area?
`
first you should set
/*
PrintPhotoPageRenderer *pageRenderer = [[PrintPhotoPageRenderer alloc]init];
pageRenderer.imageToPrint =image;
pic.printPageRenderer = pageRenderer;
*/
- (void)printImage {
// Obtain the shared UIPrintInteractionController
UIPrintInteractionController *controller = [UIPrintInteractionController sharedPrintController];
controller.delegate = self;
if(!controller){
NSLog(#"Couldn't get shared UIPrintInteractionController!");
return;
}
// We need a completion handler block for printing.
UIPrintInteractionCompletionHandler completionHandler = ^(UIPrintInteractionController *printController, BOOL completed, NSError *error) {
if(completed && error)
NSLog(#"FAILED! due to error in domain %# with error code %u", error.domain, error.code);
};
// Obtain a printInfo so that we can set our printing defaults.
UIPrintInfo *printInfo = [UIPrintInfo printInfo];
UIImage *image = ((UIImageView *)self.view).image;
[controller setDelegate:self];
printInfo.outputType = UIPrintInfoOutputPhoto;
if(!controller.printingItem && image.size.width > image.size.height)
printInfo.orientation = UIPrintInfoOrientationLandscape;
// Use this printInfo for this print job.
controller.printInfo = printInfo;
// Since the code below relies on printingItem being zero if it hasn't
// already been set, this code sets it to nil.
controller.printingItem = nil;
#if DIRECT_SUBMISSION
// Use the URL of the image asset.
if(self.imageURL && [UIPrintInteractionController canPrintURL:self.imageURL])
controller.printingItem = self.imageURL;
#endif
// If we aren't doing direct submission of the image or for some reason we don't
// have an ALAsset or URL for our image, we'll draw it instead.
if(!controller.printingItem){
// Create an instance of our PrintPhotoPageRenderer class for use as the
// printPageRenderer for the print job.
PrintPhotoPageRenderer *pageRenderer = [[PrintPhotoPageRenderer alloc]init];
// The PrintPhotoPageRenderer subclass needs the image to draw. If we were taking
// this path we use the original image and not the fullScreenImage we obtained from
// the ALAssetRepresentation.
//pageRenderer.imageToPrint = ((UIImageView *)self.view).image;
pageRenderer.imageToPrint =image;
controller.printPageRenderer = pageRenderer;
}
// The method we use presenting the printing UI depends on the type of
// UI idiom that is currently executing. Once we invoke one of these methods
// to present the printing UI, our application's direct involvement in printing
// is complete. Our delegate methods (if any) and page renderer methods (if any)
// are invoked by UIKit.
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
//[controller presentFromBarButtonItem:self.printButton animated:YES completionHandler:completionHandler]; // iPad
[controller presentFromRect:CGRectMake(0, 0, 50, 50) inView:_btnPrint animated:YES completionHandler:completionHandler];
}else
[controller presentAnimated:YES completionHandler:completionHandler]; // iPhone
}
and then you should set PrintPhotoPageRenderer
UIPrintPageRenderer.h
#import <UIKit/UIKit.h>
#interface PrintPhotoPageRenderer : UIPrintPageRenderer { UIImage
*imageToPrint; }
#property (readwrite, retain) UIImage *imageToPrint;
#end
//
PrintPhotoPageRenderer.m
#import "PrintPhotoPageRenderer.h"
#implementation PrintPhotoPageRenderer
#synthesize imageToPrint;
// This code always draws one image at print time.
-(NSInteger)numberOfPages { return 1; }
/* When using this UIPrintPageRenderer subclass to draw a photo at
print
time, the app explicitly draws all the content and need only override
the drawPageAtIndex:inRect: to accomplish that.
The following scaling algorithm is implemented here:
1) On borderless paper, users expect to see their content scaled so that there is no whitespace at the edge of the paper. So this
code scales the content to fill the paper at the expense of
clipping any content that lies off the paper.
2) On paper which is not borderless, this code scales the content so that it fills the paper. This reduces the size of the
photo but does not clip any content.
*/
- (void)drawPageAtIndex:(NSInteger)pageIndex inRect:(CGRect)printableRect {
if(self.imageToPrint){
CGSize finialSize = CGSizeMake(560, 431);//you should set width and height for you self
int x = 20;
int y = (printableRect.size.height - finialSize.height);
CGRect finalRect = CGRectMake(x, y, finialSize.width, finialSize.height);
[self.imageToPrint drawInRect:finalRect];
}else {
NSLog(#"%s No image to draw!", __func__); } }
#end

Screen corruption after dismissing modal UIImagePickerController subclass

I am having trouble dismissing a modal view controller containing a UIImagePickerController (ZBarScannerController). For some reason, after dismissing the controller after scanning a bar code with the iphone camera, the view controller always leaves a rectangle of stale graphical data over the same area as the ZBarScannerController's tool bar. The corrupt data is always a portion of whatever image the camera was seeing at the moment.
An image of the problem (corrupt area in red rectangle):
It is impossible to remove that rectangle of corrupt screen data except by backgrounding / killing the app. Also, if I specify NO when I dismiss the modal picker, the OS will remove the view controller while still displaying the controller on screen, causing crashes if I interact with any controls on the modal view. How can I go about fixing the problem?
Code for dismissing the controller:
- (void) imagePickerController: (UIImagePickerController*) reader
didFinishPickingMediaWithInfo: (NSDictionary*) info
{
id<NSFastEnumeration> results =
[info objectForKey:ZBarReaderControllerResults];
for (ZBarSymbol *oSymbol in results) {
//process result
//Ensure that QR code is decoded
zbar_symbol_type_t type = oSymbol.type;
if (type == ZBAR_QRCODE) {
//Get Barcode Data
NSString *dataStr = oSymbol.data;
[self processCommand:dataStr];
}
}
//[reader dismissModalViewControllerAnimated:NO];
[[m_oReaderController parentViewController] dismissModalViewControllerAnimated:YES];
}
Code for making the controller:
- (void)onQRCameraActivate:(id)sender {
IPOProofAppDelegate *oAppDelegate = (IPOProofAppDelegate *) [[UIApplication sharedApplication] delegate];
if (m_oReaderController == nil) {
m_oReaderController = [[ZBarReaderViewController alloc] init];
m_oReaderController.readerDelegate = self;
ZBarImageScanner *oScanner = m_oReaderController.scanner;
[oScanner setSymbology:0 config:ZBAR_CFG_ENABLE to:0];
[oScanner setSymbology:ZBAR_QRCODE config:ZBAR_CFG_ENABLE to:1];
}
[self.navigationController presentModalViewController:m_oReaderController animated:YES];
}

UIWebView - Enabling Action Sheets on <img> tags

Is it just me or has the action sheet on <img> tags been disabled in UIWebView? In Safari, e.g, when you want to save an image locally, you touch and hold on the image to get an action sheet shown. But it's not working in my custom UIWebView. I mean, it is still working for <a> tags, i.e, when I touch and hold on html links, an action sheet shows up. But not for the <img> tags.
I've tried things like putting img { -webkit-touch-callout: inherit; } in css, which didn't work. On the other hand, when I double-tap and hold on the images, a copy-balloon shows up.
So the question is, has the default action sheet callout for <img> tags been disabled for UIWebView? Is so, is there a way to re-enable it? I've googled around and saw many Q&As on how to disable it in UIWebView, so is it just me who aren't seeing the popup?
Thanks in advance!
Yes apple has disabled this feature (among others) in UIWebViews and kept it for Safari only.
However you can recreate this yourself by extending this tutorial, http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/.
Once you've finished this tutorial you'll want to add a few extra's so you can actually save images (which the tutorial doesn't cover).
I added an extra notification called #"tapAndHoldShortNotification" after 0.3 seconds which calls a method with just the disable callout code in it (to prevent both the default and your own menu popping while the page is still loading, a little bug fix).
Also to detect images you'll need to extend the JSTools.js, here's mine with the extra functions.
function MyAppGetHTMLElementsAtPoint(x,y) {
var tags = ",";
var e = document.elementFromPoint(x,y);
while (e) {
if (e.tagName) {
tags += e.tagName + ',';
}
e = e.parentNode;
}
return tags;
}
function MyAppGetLinkSRCAtPoint(x,y) {
var tags = "";
var e = document.elementFromPoint(x,y);
while (e) {
if (e.src) {
tags += e.src;
break;
}
e = e.parentNode;
}
return tags;
}
function MyAppGetLinkHREFAtPoint(x,y) {
var tags = "";
var e = document.elementFromPoint(x,y);
while (e) {
if (e.href) {
tags += e.href;
break;
}
e = e.parentNode;
}
return tags;
}
Now you can detect the user clicking on images and actually find out the images url they are clicking on, but we need to change the -(void)openContextualMenuAtPoint: method to provide extra options.
Again here's mine (I tried to copy Safari's behaviour for this):
- (void)openContextualMenuAt:(CGPoint)pt{
// Load the JavaScript code from the Resources and inject it into the web page
NSString *path = [[NSBundle mainBundle] pathForResource:#"JSTools" ofType:#"js"];
NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
[webView stringByEvaluatingJavaScriptFromString:jsCode];
// get the Tags at the touch location
NSString *tags = [webView stringByEvaluatingJavaScriptFromString:
[NSString stringWithFormat:#"MyAppGetHTMLElementsAtPoint(%i,%i);",(NSInteger)pt.x,(NSInteger)pt.y]];
NSString *tagsHREF = [webView stringByEvaluatingJavaScriptFromString:
[NSString stringWithFormat:#"MyAppGetLinkHREFAtPoint(%i,%i);",(NSInteger)pt.x,(NSInteger)pt.y]];
NSString *tagsSRC = [webView stringByEvaluatingJavaScriptFromString:
[NSString stringWithFormat:#"MyAppGetLinkSRCAtPoint(%i,%i);",(NSInteger)pt.x,(NSInteger)pt.y]];
UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil];
selectedLinkURL = #"";
selectedImageURL = #"";
// If an image was touched, add image-related buttons.
if ([tags rangeOfString:#",IMG,"].location != NSNotFound) {
selectedImageURL = tagsSRC;
if (sheet.title == nil) {
sheet.title = tagsSRC;
}
[sheet addButtonWithTitle:#"Save Image"];
[sheet addButtonWithTitle:#"Copy Image"];
}
// If a link is pressed add image buttons.
if ([tags rangeOfString:#",A,"].location != NSNotFound){
selectedLinkURL = tagsHREF;
sheet.title = tagsHREF;
[sheet addButtonWithTitle:#"Open"];
[sheet addButtonWithTitle:#"Copy"];
}
if (sheet.numberOfButtons > 0) {
[sheet addButtonWithTitle:#"Cancel"];
sheet.cancelButtonIndex = (sheet.numberOfButtons-1);
[sheet showInView:webView];
}
[selectedLinkURL retain];
[selectedImageURL retain];
[sheet release];
}
(NOTES: selectedLinkURL and selectedImageURL are declared in the .h file to let them be accessed throughout the class, for saving or opening the link latter.
So far we've just been going back over the tutorials code making changes but now we will move into what the tutorial doesn't cover (it stops before actually mentioning how to handle saving the images or opening the links).
To handle the users choice we now need to add the actionSheet:clickedButtonAtIndex: method.
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex{
if ([[actionSheet buttonTitleAtIndex:buttonIndex] isEqualToString:#"Open"]){
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:selectedLinkURL]]];
}
else if ([[actionSheet buttonTitleAtIndex:buttonIndex] isEqualToString:#"Copy"]){
[[UIPasteboard generalPasteboard] setString:selectedLinkURL];
}
else if ([[actionSheet buttonTitleAtIndex:buttonIndex] isEqualToString:#"Copy Image"]){
[[UIPasteboard generalPasteboard] setString:selectedImageURL];
}
else if ([[actionSheet buttonTitleAtIndex:buttonIndex] isEqualToString:#"Save Image"]){
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(saveImageURL:) object:selectedImageURL];
[queue addOperation:operation];
[operation release];
}
}
This checks what the user wants to do and handles /most/ of them, only the "save image" operation needs another method to handle that. For the progress I used MBProgressHub.
Add an MBProgressHUB *progressHud; to the interface declaration in the .h and set it up in the init method (of whatever class you're handling the webview from).
progressHud = [[MBProgressHUD alloc] initWithView:self.view];
progressHud.customView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Tick.png"]] autorelease];
progressHud.opacity = 0.8;
[self.view addSubview:progressHud];
[progressHud hide:NO];
progressHud.userInteractionEnabled = NO;
And the -(void)saveImageURL:(NSString*)url; method will actually save it to the image library.
(A better way would be to do the download through an NSURLRequest and update the progress hud in MBProgressHUDModeDeterminate to deflect how long it'll actually take to download, but this is a more hacked together implementation then that)
-(void)saveImageURL:(NSString*)url{
[self performSelectorOnMainThread:#selector(showStartSaveAlert) withObject:nil waitUntilDone:YES];
UIImageWriteToSavedPhotosAlbum([UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url]]], nil, nil, nil);
[self performSelectorOnMainThread:#selector(showFinishedSaveAlert) withObject:nil waitUntilDone:YES];
}
-(void)showStartSaveAlert{
progressHud.mode = MBProgressHUDModeIndeterminate;
progressHud.labelText = #"Saving Image...";
[progressHud show:YES];
}
-(void)showFinishedSaveAlert{
// Set custom view mode
progressHud.mode = MBProgressHUDModeCustomView;
progressHud.labelText = #"Completed";
[progressHud performSelector:#selector(hide:) withObject:[NSNumber numberWithBool:YES] afterDelay:0.5];
}
And of cause add [progressHud release]; to the dealloc method.
Hopefully this shows you how to add some of the options to a webView that apple left out.
Of cause though you can add more things to this like a "Read Later" option for instapaper or a "Open In Safari" button.
(looking at the length of this post I'm seeing why the original tutorial left out the finial implementation details)
Edit: (updated with more info)
I was asked about the detail I glossed over at the top, the #"tapAndHoldShortNotification", so this is clarifying it.
This is my UIWindow subclass, it adds the second notification to cancel the default selection menu (this is because when I tried the tutorial it showed both menus).
- (void)tapAndHoldAction:(NSTimer*)timer {
contextualMenuTimer = nil;
UIView* clickedView = [self hitTest:CGPointMake(tapLocation.x, tapLocation.y) withEvent:nil];
while (clickedView != nil) {
if ([clickedView isKindOfClass:[UIWebView class]]) {
break;
}
clickedView = clickedView.superview;
}
if (clickedView) {
NSDictionary *coord = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:tapLocation.x],#"x",
[NSNumber numberWithFloat:tapLocation.y],#"y",nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"TapAndHoldNotification" object:coord];
}
}
- (void)tapAndHoldActionShort:(NSTimer*)timer {
UIView* clickedView = [self hitTest:CGPointMake(tapLocation.x, tapLocation.y) withEvent:nil];
while (clickedView != nil) {
if ([clickedView isKindOfClass:[UIWebView class]]) {
break;
}
clickedView = clickedView.superview;
}
if (clickedView) {
NSDictionary *coord = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:tapLocation.x],#"x",
[NSNumber numberWithFloat:tapLocation.y],#"y",nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"TapAndHoldShortNotification" object:coord];
}
}
- (void)sendEvent:(UIEvent *)event {
NSSet *touches = [event touchesForWindow:self];
[touches retain];
[super sendEvent:event]; // Call super to make sure the event is processed as usual
if ([touches count] == 1) { // We're only interested in one-finger events
UITouch *touch = [touches anyObject];
switch ([touch phase]) {
case UITouchPhaseBegan: // A finger touched the screen
tapLocation = [touch locationInView:self];
[contextualMenuTimer invalidate];
contextualMenuTimer = [NSTimer scheduledTimerWithTimeInterval:0.8 target:self selector:#selector(tapAndHoldAction:) userInfo:nil repeats:NO];
NSTimer *myTimer;
myTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(tapAndHoldActionShort:) userInfo:nil repeats:NO];
break;
case UITouchPhaseEnded:
case UITouchPhaseMoved:
case UITouchPhaseCancelled:
[contextualMenuTimer invalidate];
contextualMenuTimer = nil;
break;
}
} else { // Multiple fingers are touching the screen
[contextualMenuTimer invalidate];
contextualMenuTimer = nil;
}
[touches release];
}
The notification is then handled like this:
// in -viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(stopSelection:) name:#"TapAndHoldShortNotification" object:nil];
- (void)stopSelection:(NSNotification*)notification{
[webView stringByEvaluatingJavaScriptFromString:#"document.documentElement.style.webkitTouchCallout='none';"];
}
It's only a little change but it fixes the annoying little bug where you get 2 menus appear (the standard one and yours).
Also you could easily add iPad support by sending the touches location as the notification fires and then showing the UIActionSheet from that point, though this was written before the iPad so doesn't include support for that.
After struggling for, like 2 or 3 days non-stop on this problem, it seems like the position is computed "relatively" to the UIWebView's "TOP-LEFT" corner (I am programing for iOS 7).
So, to make this work, when you get the position, on the controller where your WebView is (i'll put a snippet of my code below), don't add the "scroll-offset"
SNIPPET - ContextualMenuAction:
- (void)contextualMenuAction:(NSNotification*)notification {
// Load javascript
[self loadJavascript];
// Initialize the coordinates
CGPoint pt;
pt.x = [[[notification object] objectForKey:#"x"] floatValue];
pt.y = [[[notification object] objectForKey:#"y"] floatValue];
// Convert point from window to view coordinate system
pt = [self.WebView convertPoint:pt fromView:nil];
// Get PAGE and UIWEBVIEW dimensions
CGSize pageDimensions = [self.WebView documentSize];
CGSize webviewDimensions = self.WebView.frame.size;
/***** If the page is in MOBILE version *****/
if (webviewDimensions.width == pageDimensions.width) {
}
/***** If the page is in DESKTOP version *****/
else {
// convert point from view to HTML coordinate system
CGSize viewSize = [self.WebView frame].size;
// Contiens la portion de la page visible depuis la webview (en fonction du zoom)
CGSize windowSize = [self.WebView windowSize];
CGFloat factor = windowSize.width / viewSize.width;
CGFloat factorHeight = windowSize.height / viewSize.height;
NSLog(#"factor: %f", factor);
pt.x = pt.x * factor; // ** logically, we would add the offset **
pt.y = pt.y * factorHeight; // ** logically, we would add the offset **
}
NSLog(#"x: %f and y: %f", pt.x, pt.y);
NSLog(#"WINDOW: width: %f height: %f", [self.WebView windowSize].width, [self.WebView windowSize].height);
NSLog(#"DOCUMENT: width: %f height: %f", pageDimensions.width, pageDimensions.height);
[self openContextualMenuAt:pt];
}
SNIPPET - in openContextualMenuAt:
To load the correct JS function:
- (void)openContextualMenuAt:(CGPoint)pt {
// Load javascript
[self loadJavascript];
// get the Tags at the touch location
NSString *tags = [self.WebView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:#"getHTMLTagsAtPoint(%li,%li);",(long)pt.x,(long)pt.y]];
...
}
SNIPPET - in JSTools.js:
This is the function I use to get the element touched
function getHTMLTagsAtPoint(x,y) {
var tags = ",";
var element = document.elementFromPoint(x,y);
while (element) {
if (element.tagName) {
tags += element.tagName + ',';
}
element = element.parentNode;
}
return tags;
}
SNIPPET - loadJavascript
I use this one to inject my JS code in the webview
-(void)loadJavascript {
[self.WebView stringByEvaluatingJavaScriptFromString:
[NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"JSTools" ofType:#"js"] encoding:NSUTF8StringEncoding error:nil]];
}
This part (everything I did to overrride the default UIActionSheet) is HEAVILY (should I say completely) based on
this post
#Freerunning's answer is complete (i did almost everything he said in my other classes, like on the post my code is based on), the snippets i posted is just to show you more "completely" how my code is.
Hope this helps! ^^
First of all thanks to Freerunnering for the great solution!
But you can do this with an UILongPressGestureRecognizer instead of a custom LongPressRecognizer. This makes things a bit easier to implement:
In the Viewcontroller Containing the webView:
Add UIGestureRecognizerDelegate to your ViewController
let mainJavascript = "function MyAppGetHTMLElementsAtPoint(x,y) { var tags = \",\"; var e = document.elementFromPoint(x,y); while (e) { if (e.tagName) { tags += e.tagName + ','; } e = e.parentNode; } return tags; } function MyAppGetLinkSRCAtPoint(x,y) { var tags = \"\"; var e = document.elementFromPoint(x,y); while (e) { if (e.src) { tags += e.src; break; } e = e.parentNode; } return tags; } function MyAppGetLinkHREFAtPoint(x,y) { var tags = \"\"; var e = document.elementFromPoint(x,y); while (e) { if (e.href) { tags += e.href; break; } e = e.parentNode; } return tags; }"
func viewDidLoad() {
...
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(CustomViewController.longPressRecognizerAction(_:)))
self.webView.scrollView.addGestureRecognizer(longPressRecognizer)
longPressRecognizer.delegate = self
...
}
func longPressRecognizerAction(sender: UILongPressGestureRecognizer) {
if sender.state == UIGestureRecognizerState.Began {
let tapPostion = sender.locationInView(self.webView)
let tags = self.webView.stringByEvaluatingJavaScriptFromString("MyAppGetHTMLElementsAtPoint(\(tapPostion.x),\(tapPostion.y));")
let href = self.webView.stringByEvaluatingJavaScriptFromString("MyAppGetLinkHREFAtPoint(\(tapPostion.x),\(tapPostion.y));")
let src = self.webView.stringByEvaluatingJavaScriptFromString("MyAppGetLinkSRCAtPoint(\(tapPostion.x),\(tapPostion.y));")
print("tags: \(tags)\nhref: \(href)\nsrc: \(src)")
// handle the results, for example with an UIDocumentInteractionController
}
}
// Without this function, the customLongPressRecognizer would be replaced by the original UIWebView LongPressRecognizer
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
And thats it!

How to check if a view is valid iphone

I am having trouble with my UITableView in the NavigationController.
When I add data to the table I use another class to download images to display in that table, while all that works great but if in the middle of images being download I swtich back to the previous view in the navigationcontroller app crashed.
Here is my code to explain further
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Set appIcon and clear temporary data/image
UIImage *image = [[UIImage alloc] initWithData:self.activeDownload];
UIImage *appIcon;
if (image.size.width != kAppIconHeight && image.size.height != kAppIconHeight)
{
CGSize itemSize = CGSizeMake(125, 85);
UIGraphicsBeginImageContext(itemSize);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[image drawInRect:imageRect];
appIcon = UIGraphicsGetImageFromCurrentImageContext();
///UIGraphicsEndImageContext();
}
else
{
appIcon = image;
//self.appRecord.appIcon = image;
}
self.activeDownload = nil;
// Release the connection now that it's finished
self.imageConnection = nil;
// call our delegate and tell it that our icon is ready for display
if(delegate != nil)
[delegate appImageDidLoad:self.indexPathInTableView imaged:appIcon ];
[image release];
}
The appImageDidLoad is a method that exists in my UITableView view.
Is there a way I can check to see if the UITableView is valid in my imagedownload class so I know not to send the image.
Thanks in advance.
The crash is due to delegate getting release by the time the image was ready!
Try this in ViewWillDisappear
// terminate all pending download connections
NSArray *allDownloads = [self.imageDownloadsInProgress allValues];
[allDownloads performSelector:#selector(cancelDownload)];
This is the solution for this crash.
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
// terminate all pending download connections
NSArray *allDownloads = [self.imageDownloadsInProgress1 allValues];
[allDownloads makeObjectsPerformSelector:#selector(cancelDownload)];
}