Why is textView:shouldInteractWithTextAttachment:inRange: never getting called on UITextView delegate? - ios

Is anyone using this delegate method ? I get callbacks on
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
But not on this one. The documentation seems a bit ambiguous about what this is intended for
- (BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange
According to the documentation on the Web this is what its intended for:
Discussion
The text view calls this method if the user taps or long-presses the text attachment and its image property is not nil. Implementation of this method is optional. You can use this method to trigger an action in addition to displaying the text attachment inline with the text.
And here is Xcode 5 documentation:
Asks the delegate if the specified text view should display the provided text attachment in the given range of text.
The text view calls this method when a text attachment character is recognized in its text container by a data detector. Implementation of this method is optional. You can use this method to trigger an alternative action besides displaying the text attachment inline with the text in the given range.
EDIT:
Mmm OK I figured out the problem. If I paste an image in from iOS then it works, however if the image was pasted in from OS X it does not. It seems that the actual attachment formats used are not quite the same on both platforms despite the fact that the image appears to show up correctly in the text views. On closer inspection the NSTextAttachment classes don't appear to be the same on iOS as on OS X.
If anyone can shed any light on the cross platform compatibility here please do.
Also if I save the attributed string after pasting the image in on iOS and then retrieve it and display it in the UITextView interaction with the attachment is no longer possible. It would appear that when storing the image the image is actually placed in contents if contents is nil. So maybe I am going to have to iterate through all attachments to check what data is stored where particularly to figure out any differences in behaviour across the OS X and iOS platforms.
FURTHER EDIT:
The method only gets called if the attachment image is NOT nil and despite the fact that an image is displayed the actual image attribute can actually be nil, silly me! Anyway the fix seems to be to check all the attachments in the attributed string and to set their image attribute to something, usually the contents of the fileWrapper. The default NSTextAttachment behaviour seems to be to store the image in the fileWrapper when its archived but it does not do the reverse when its unarchived. Anyway I want to retain the original image in the attachment but depending on the device display a suitably scaled version of the original !

The chief thing is that the text view's editable property must be NO and it's selectable property must be YES. Those are the only circumstances under which this delegate method is called. If you are getting shouldBeginEditing then your text field is editable which is exactly what it must not be.

Here is what I do to ensure the NSTextAttachments image attribute gets set when restoring the UITextView's attributed string from archived data (in this case whenever the user selects a record from a Core Data store).
I set the UITextView up as a delegate for textStorage and in the didProcessEditing look for any attachments that may have been added and then check that their image attribute is set. I am also setting the scaling factor on the image to make sure the image scales appropriately for the device.
This way I don't loose the original resolution of the image and if the user wants to view it in more detail I provide the option to open it in an image browser window from a popup menu.
Hope this helps someone else.
EDIT:
Check here for more details on NSTextView and UITextView http://ossh.com.au/design-and-technology/software-development/
- (void)textStorage:(NSTextStorage *)textStorage didProcessEditing:(NSTextStorageEditActions)editedMask range:(NSRange)editedRange changeInLength:(NSInteger)delta {
//FLOG(#"textStorage:didProcessEditing:range:changeInLength: called");
[textStorage enumerateAttributesInRange:editedRange options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:
^(NSDictionary *attributes, NSRange range, BOOL *stop) {
// Iterate over each attribute and look for a Font Size
[attributes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([[key description] isEqualToString:NSAttachmentAttributeName]) {
NSTextAttachment *attachment = obj;
//Reset the image attribute and scale for the device size if necessary
[self resetAttachmentImage:attachment];
}
}];
}];
}
- (void)resetAttachmentImage:(NSTextAttachment*)attachment {
UIImage *image = [attachment image];
float factor = 2;
if (image == nil) {
if (attachment.fileWrapper == nil || !attachment.fileWrapper.isRegularFile) {
attachment.image = [UIImage imageNamed:#"unknown_attachment.png"];
return;
}
//Usually retrieved from store
image = [UIImage imageWithData:attachment.fileWrapper.regularFileContents];
} else {
// Convert any pasted image
image = [UIImage imageWithData:UIImagePNGRepresentation(image)];
}
float imgWidth = image.size.width;
// If its wider than the view scale it to fit
if (imgWidth > _viewWidth) {
factor = imgWidth / _viewWidth + 0.5;
attachment.image = [UIImage imageWithData:UIImagePNGRepresentation(image) scale:factor];
} else {
attachment.image = [UIImage imageWithData:UIImagePNGRepresentation(image) scale:_scale];
}
}

Related

Create PDF with proper format and Images in Objective - C

Please read my scenario carefully,
I have one UITextView and one UIImageView bottom of TextView.
Each time there will be dynamic content in TextView and accordingly that, I am asking User to make a signature and it will be displayed as an image in bottom ImageView.
Now the requirement is I have to pass these details on the server along with Signature in one PDF file, So I have to create PDF file which contains both TextView text and ImageView image.
Note: TextView is containing Html text also, so it should show in the same format in PDF also.
Check below Images as required and current pdfs.
This is required PDF
This is current PDF
Only Put the code which can be helpful for both HTML support and Image merge with text. Please don't show simple PDF creation as I have done it already.
you don't need a 3rd party library, Cocoa and Cocoa touch have rich PDF support. I've stubbed you out a little start, do this in your viewController. There may be a few small errors, Ive been using swift for a couple of years now but I used my very rusty objC here because you tagged the question that way. Let me know any problems, good luck
-(NSData *)drawPDFdata{
// default pdf..
// 8.5 X 11 inch #72dpi
// = 612 x 792
CGRect rct = {{0.0 , 0.0 } , {612.0 , 792.0}}
NSMutableData *pdfData = [[NSMutableData alloc]init];
UIGraphicsBeginPDFContextToData(pdfData, rct, nil);
CGContextRef pdfContext = UIGraphicsGetCurrentContext();
UIGraphicsBeginPDFPage();
//textView drawing
CGContextSaveGState(pdfContext);
CGContextConcatCTM(pdfContext, CGAffineTransformMakeTranslation(50.0,50.0));//this is just an offset for the textView drawing. You will want to play with the values, espeecially if supporting multiple screen sizes you might tranform the scale as well..
[textView.layer renderInContext:pdfContext]
CGContextRestoreGState(pdfContext);
//imageView drawing
CGContextSaveGState(pdfContext);
CGContextConcatCTM(pdfContext, CGAffineTransformMakeTranslation(50.0,50.0)); //this is just an offset for the imageView drawing. Thee same stuff applies as above..
[imageView.layer renderInContext:pdfContext]
CGContextRestoreGState(pdfContext);
//cleanup
UIGraphicsEndPDFContext();
return pdfData;
}
here's a couple of client functions to use this NSData
//ways to use the pdf Data
-(Bool)savePDFtoPath: (NSString *)path {
return [ [self drawPDFdata] writeToFile:path atomically:YES] ;
}
//requires Quartz framework.. (can be drawn straight to a UIView)
// note you MAY owe a CGPDFDocumentRelease() on the result of this function (sorry i've not used objC in a couple of years...)
-(CGPDFDocument *)createPDFdocument {
NSData *data = [self drawPDFdata];
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL , data , sizeof(data) ,NULL);
CGPDFDocument result = CGPDFDocumentCreateWithProvider(provider);
CGDataProviderRelease(provider); //not sure if this is still required under ARC?? (def not in swift)
return result;
}
Try this useful third party library :
https://github.com/iclems/iOS-htmltopdf
Use this function for your problem :
+ (id)createPDFWithHTML:(NSString*)HTML pathForPDF:(NSString*)PDFpath pageSize:(CGSize)pageSize margins:(UIEdgeInsets)pageMargins successBlock:(NDHTMLtoPDFCompletionBlock)successBlock errorBlock:(NDHTMLtoPDFCompletionBlock)errorBlock;

Keep text in UITextView highlighted when the popover is dismissed

I have a UIPopover that shows up a plain view containing a UITextView filled with some text. I have managed to highlight the text. When the popover is dismissed, and re-opened, the highlight disappears. I want to keep the text highlighted even if if the application is closed. Any ideas how to achieve that?The code i used is the following :
- (void)highlight {
NSRange selectedRange = self.textViewAll.selectedRange;
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
initWithAttributedString:self.textViewAll.attributedText];
[attributedString addAttribute:NSForegroundColorAttributeName
value:[UIColor redColor]
range:selectedRange];
// [highlightedRange addObject:];
// This is where i tried to save each location and length in a mutable array but didn't work
[highlightedRangeLocation insertObject:[NSNumber numberWithInteger:selectedRange.location] atIndex:indexOfHighlight];
[highlightedRangeLength insertObject:[NSNumber numberWithInteger:selectedRange.length] atIndex:indexOfHighlight];
///////////////////////////////////////////////////////////////////////////////
self.textViewAll.attributedText = attributedString;
indexOfHighlight ++ ;
}
- (void)viewDidLoad {
UIMenuItem *highlightMenuItem = [[UIMenuItem alloc] initWithTitle:#"Highlight" action:#selector(highlight)];
[[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObject:highlightMenuItem]];
float sysVer = [[[UIDevice currentDevice] systemVersion] floatValue];
if (sysVer >= 8.0) {
self.textViewAll.layoutManager.allowsNonContiguousLayout = NO;
}
}
Could anyone point out how to continue from here?
Edit 1 :
The code that close the popover :
- (IBAction)closeFun:(id)sender {
// self.popoverPresentationController set
[self dismissViewControllerAnimated:YES completion:nil];
// [self dismis]
}
Can't you juste save the Highlighted text range in [NSUserDefaults standardUserDefaults] whenever the popover is dismissed, and retrieve it when the popover reappears ?
I think the problem is in the fact that the popover is responsible for the highlighted state, i.e .it is the popover who keeps that fact/state.
The popover is a part of presentation layer / user interface. Surely the highlight represents some fact that ( now comes the catch ) - is completely independent of the popover.
For example highlighting a task could represent that the task is due. Or, highlighting a label to red color could mean that the balance in the bank is in negative numbers.
You see, no matter what user interface element you use, they only represent some underlying business reality.
But what probably happens you create a popover instance, you set it to have a highlighted element. But then this concrete popover instance dies, when it is closed.
And the highlight dies with it.
When you click some button (I guess), a popover shows up, but it is a different instance. This instance doesn't know about highlight.
Even if you somehow managed to keep the one instance of popover alive, and just hide and show it again, the popover should NOT be responsible to know whether something is red or due, (and thus highlighted.)
In you application, you should have a well separated model layer...which is basically a set of related objects that represent state ie. fact that are related to what the application solves from business perspective (for ex. draws lines, calculates interest..stores music..anything really). This model layer, some object in it, should store the facts..ie.e. the task is due, or the balance is low.
Every time you show your popover, you should investigate what are the underlying facts in your model layer right when the popover is being shown. Ivestigating means find a programmatic way to look into model objects, find out about values there and and set up the highlight in that moment again based on this "investigation". You should not rely on the fact that it was highlighted in the not so distant past.

AutoLayout row height miscalculating for NSAttributedString

My app pulls HTML from an API, converts it into a NSAttributedString (in order to allow for tappable links) and writes it to a row in an AutoLayout table. Trouble is, any time I invoke this type of cell, the height is miscalculated and the content is cut off. I have tried different implementations of row height calculations, none of which work correctly.
How can I accurately, and dynamically, calculate the height of one of these rows, while still maintaining the ability to tap HTML links?
Example of undesired behavior
My code is below.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
switch(indexPath.section) {
...
case kContent:
{
FlexibleTextViewTableViewCell* cell = (FlexibleTextViewTableViewCell*)[TableFactory getCellForIdentifier:#"content" cellClass:FlexibleTextViewTableViewCell.class forTable:tableView withStyle:UITableViewCellStyleDefault];
[self configureContentCellForIndexPath:cell atIndexPath:indexPath];
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.desc.font = [UIFont fontWithName:[StringFactory defaultFontType] size:14.0f];
return cell;
}
...
default:
return nil;
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UIFont *contentFont = [UIFont fontWithName:[StringFactory defaultFontType] size:14.0f];
switch(indexPath.section) {
...
case kContent:
return [self textViewHeightForAttributedText:[self convertHTMLtoAttributedString:myHTMLString] andFont:contentFont andWidth:self.tappableCell.width];
break;
...
default:
return 0.0f;
}
}
-(NSAttributedString*) convertHTMLtoAttributedString: (NSString *) html {
return [[NSAttributedString alloc] initWithData:[html dataUsingEncoding:NSUTF8StringEncoding]
options:#{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: #(NSUTF8StringEncoding)}
documentAttributes:nil
error:nil];
}
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andFont:(UIFont *)font andWidth:(CGFloat)width {
NSMutableAttributedString *mutableText = [[NSMutableAttributedString alloc] initWithAttributedString:text];
[mutableText addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, text.length)];
UITextView *calculationView = [[UITextView alloc] init];
[calculationView setAttributedText:mutableText];
CGSize size = [self text:mutableText.string sizeWithFont:font constrainedToSize:CGSizeMake(width,FLT_MAX)];
CGSize sizeThatFits = [calculationView sizeThatFits:CGSizeMake(width, FLT_MAX)];
return sizeThatFits.height;
}
In the app I'm working on, the app pulls terrible HTML strings from a lousy API written by other people and converts HTML strings to NSAttributedString objects. I have no choice but to use this lousy API. Very sad. Anyone who has to parse terrible HTML string knows my pain. I use Text Kit. Here is how:
parse html string to get DOM object. I use libxml with a light wrapper, hpple. This combination is super fast and easy to use. Strongly recommended.
traverse the DOM object recursively to construct NSAttributedString object, use custom attribute to mark links, use NSTextAttachment to mark images. I call it rich text.
create or reuse primary Text Kit objects. i.e. NSLayoutManager, NSTextStorage, NSTextContainer. Hook them up after allocation.
layout process
Pass the rich text constructed in step 2 to the NSTextStorage object in step 3. with [NSTextStorage setAttributedString:]
use method [NSLayoutManager ensureLayoutForTextContainer:] to force layout to happen
calculate the frame needed to draw the rich text with method [NSLayoutManager usedRectForTextContainer:]. Add padding or margin if needed.
rendering process
return the height calculated in step 5 in [tableView: heightForRowAtIndexPath:]
draw the rich text in step 2 with [NSLayoutManager drawGlyphsForGlyphRange:atPoint:]. I use off-screen drawing technique here so the result is an UIImage object.
use an UIImageView to render the final result image. Or pass the result image object to the contents property of layer property of contentView property of UITableViewCell object in [tableView:cellForRowAtIndexPath:].
event handling
capture touch event. I use a tap gesture recognizer attached with the table view.
get the location of touch event. Use this location to check if user tapped a link or an image with [NSLayoutManager glyphIndexForPoint:inTextContainer:fractionOfDistanceThroughGlyph] and [NSAttributedString attribute:atIndex:effectiveRange:].
Event handling code snippet:
CGPoint location = [tap locationInView:self.tableView];
// tap is a tap gesture recognizer
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
if (!indexPath) {
return;
}
CustomDataModel *post = [self getPostWithIndexPath:indexPath];
// CustomDataModel is a subclass of NSObject class.
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
location = [tap locationInView:cell.contentView];
// the rich text is drawn into a bitmap context and rendered with
// cell.contentView.layer.contents
// The `Text Kit` objects can be accessed with the model object.
NSUInteger index = [post.layoutManager
glyphIndexForPoint:location
inTextContainer:post.textContainer
fractionOfDistanceThroughGlyph:NULL];
CustomLinkAttribute *link = [post.content.richText
attribute:CustomLinkAttributeName
atIndex:index
effectiveRange:NULL];
// CustomLinkAttributeName is a string constant defined in other file
// CustomLinkAttribute is a subclass of NSObject class. The instance of
// this class contains information of a link
if (link) {
// handle tap on link
}
// same technique can be used to handle tap on image
This approach is much faster and more customizable than [NSAttributedString initWithData:options:documentAttributes:error:] when rendering same html string. Even without profiling I can tell the Text Kit approach is faster. It's very fast and satisfying even though I have to parse html and construct attributed string myself. The NSDocumentTypeDocumentAttribute approach is too slow thus is not acceptable. With Text Kit, I can also create complex layout like text block with variable indentation, border, any-depth nested text block, etc. But it does need to write more code to construct NSAttributedString and to control layout process. I don't know how to calculate the bounding rect of an attributed string created with NSDocumentTypeDocumentAttribute. I believe attributed strings created with NSDocumentTypeDocumentAttribute are handled by Web Kit instead of Text Kit. Thus is not meant for variable height table view cells.
EDIT:
If you must use NSDocumentTypeDocumentAttribute, I think you have to figure out how the layout process happens. Maybe you can set some breakpoints to see what object is responsible for layout process. Then maybe you can query that object or use another approach to simulate the layout process to get the layout information. Some people use an ad-hoc cell or a UITextView object to calculate height which I think is not a good solution. Because in this way, the app has to layout the same chunk of text at least twice. Whether you know or not, somewhere in your app, some object has to layout the text just so you can get information of layout like bounding rect. Since you mentioned NSAttributedString class, the best solution is Text Kit after iOS 7. Or Core Text if your app is targeted on earlier iOS version.
I strongly recommend Text Kit because in this way, for every html string pulled from API, the layout process only happens once and layout information like bounding rect and positions of every glyph are cached by NSLayoutManager object. As long as the Text Kit objects are kept, you can always reuse them. This is extremely efficient when using table view to render arbitrary length text because text are laid out only once and drawn every time a cell is needed to display. I also recommend use Text Kit without UITextView as the official apple docs suggested. Because one must cache every UITextView if he wants to reuse the Text Kit objects attached with that UITextView. Attach Text Kit objects to model objects like I do and only update NSTextStorage and force NSLayoutManager to layout when a new html string is pulled from API. If the number of rows of table view is fixed, one can also use a fixed list of placeholder model objects to avoid repeat allocation and configuration. And because drawRect: causes Core Animation to create useless backing bitmap which must be avoided, do not use UIView and drawRect:. Either use CALayer drawing technique or draw text into a bitmap context. I use the latter approach because that can be done in a background thread with GCD, thus the main thread is free to respond to user's operation. The result in my app is really satisfying, it's fast, the typesetting is nice, the scrolling of table view is very smooth (60 fps) since all the drawing process are done in background threads with GCD. Every app needs to draw some text with table view should use Text Kit.
You need to update intrinsic content size.
I assume that you set attributed text to label in this code [self configureContentCellForIndexPath:cell atIndexPath:indexPath];
So, it should look like this
cell.youLabel.attributedText = NSAttributedString(...)
cell.youLabel.invalidateIntrinsicContentSize()
cell.youLabel.layoutIfNeeded()
You height calculation code (CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andFont:(UIFont *)font andWidth:(CGFloat)width should be replaced with cell height calculation using prototyping cell.
I'm assuming you are using a UILabel to display the string?
If you are, I have had countless issues with multiline labels with autoLayout. I provided an answer here
Table View Cell AutoLayout in iOS8
which also references another answer of mine that has a breakdown of how i've solved all my issues. Similar issues have cropped up again in iOS 8 that require a similar fix in a different area.
All comes down to the idea of setting the UILabel's preferredMaxLayoutWidth every time is bounds change. What also helped is setting the cells width to be the width of the tableview before running:
CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
I ran into a very similar issue on another project where fields using NSAttributedString weren't rendering with the correct height. Unfortunately, there are two bugs with it that made us completely drop using it in our project.
The first is a bug that you've noticed here, where some HTML will cause an incorrect size calculation. This is usually from the space between the p tags. Injecting CSS sort of solved the issue, but we had no control over the incoming format. This behaves differently between iOS7 and iOS8 where it's wrong on one and right on the other.
The second (and more serious) bug is that NSAttributedString is absurdly slow in iOS 8. I outlined it here: NSAttributedString performance is worse under iOS 8
Rather than making a bunch of hacks to have everything perform as we wanted, the suggestion of using https://github.com/Cocoanetics/DTCoreText worked out really well for the project.
If you can target iOS 8 using dynamic cell sizing is the ideal solution to your problem.
To use dynamic cell sizing, delete heightForRowAtIndexPath: and set self.tableView.rowHeight to UITableViewAutomaticDimension.
Here is a video with more details:
https://developer.apple.com/videos/wwdc/2014/?include=226#226
You can replace this method to calculate the height of attributed string:
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andFont:(UIFont *)font andWidth:(CGFloat)width {
CGFloat result = font.pointSize + 4;
if (text)
result = (ceilf(CGRectGetHeight([text boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil])) + 1);
return result;
}
Maybe the font you changed doesnt matches with the font of content on html pages. So, use this method to create attributed string with appropriate font:
// HTML -> NSAttributedString
-(NSAttributedString*) convertHTMLtoAttributedString: (NSString *) html {
NSError *error;
NSDictionary *options = #{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithData:[html dataUsingEncoding:NSUTF8StringEncoding] options:options documentAttributes:nil error:&error];
if(!attrString) {
NSLog(#"creating attributed string from HTML failed: %#", error.debugDescription);
}
return attrString;
}
// force font thrugh & css
- (NSAttributedString *)attributedStringFromHTML:(NSString *)html withFont:(UIFont *)font {
return [self convertHTMLtoAttributedString:[NSString stringWithFormat:#"<span style=\"font-family: %#; font-size: %f\";>%#</span>", font.fontName, font.pointSize, html]];
}
and in your tableView:heightForRowAtIndexPath: replace it with this:
case kContent:
return [self textViewHeightForAttributedText:[self attributedStringFromHTML:myHTMLString withFont:contentFont] andFont:contentFont andWidth:self.tappableCell.width];
break;
You should be able to convert to an NSString to calculate the height like this.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
UIFont * font = [UIFont systemFontOfSize:15.0f];
NSString *text = [getYourAttributedTextArray objectAtIndex:indexPath.row] string];
CGFloat height = [text boundingRectWithSize:CGSizeMake(self.tableView.frame.size.width, maxHeight) options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) attributes:#{NSFontAttributeName: font} context:nil].size.height;
return height + additionalHeightBuffer;
}
[cell.descriptionLabel setPreferredMaxLayoutWidth:375.0];

Screenshot Safari from Share Extension

Is it possible to perform a screenshot of the current visible zone of the webview in Safari from a Share Extension? I could use windows, but UIApplication isn't supported on extensions so I can't access to that window.
You can't since UIApplication can't be reached from an extension. You cannot get the first UIWindow, which is the Safari layer, so you have to play with the Javascript preprocessing file that the extensions have. So just create a Javascript file that, when sent to Safari, generates a base64 string with the current visible zone image data. Take that string through the kUTTypePropertyList identifier in your extension. Since that should be NSData, generate the UIImage from there, by using +imageWithData. That is what you're looking for, without having to load the page again, preventing a second load and a bad image if the webpage requires of a login.
As far as I know, you can't unless you invoke the API you need dynamically, and even so you might run into context permission issues and app store approval issues.
An alternative might be passing the current Safari URL to your extension, load it using a hidden UIWebView and render this view into an UIImage but you will loose the current visible zone information...
Edit: So the below works in the Simulator but does not work on the device. I'm presently looking for a solution as well.
You can't get just the visible area of Safari, but you can get a screenshot with a little ingenuity. The following method captures a screenshot from a ShareViewController.
func captureScreen() -> UIImage
{
// Get the "screenshot" view.
let view = UIScreen.mainScreen().snapshotViewAfterScreenUpdates(false)
// Add the screenshot view as a subview of the ShareViewController's view.
self.view.addSubview(view);
// Now screenshot *this* view.
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, false, 0);
self.view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Finally, remove the subview.
view.removeFromSuperview()
return image
}
This is the approved way to capture the screenshot of a webpage in a share extension:
for (NSExtensionItem *item in self.extensionContext.inputItems) {
for (NSItemProvider *itemProvider in item.attachments) {
[itemProvider loadPreviewImageWithOptions:#{NSItemProviderPreferredImageSizeKey: [NSValue valueWithCGSize:CGSizeMake(60.0f, 60.0f)]} completionHandler:^(UIImage * item, NSError * _Null_unspecified error) {
// Set the size to that desired, however,
// Note that the image 'item' returns will not necessarily by the size that you requested, so code should handle that case.
// Use the UIImage however you wish here.
}];
}
}

Redrawing NSTextAttachments in an UITextView with attributed text

I have a NSTextAttachment subclass with overridden attachmentBoundsForTextContainer:proposedLineFragment:glyphPosition:characterIndex: imageForBounds:textContainer:characterIndex:.
I need to redraw the attachment(s) at some point. Calling setNeedsDisplay on the UITextView doesn't work.
Any ideas? I'd like to avoid recreating attachments and/or the attributed string.
You'll want to use one of textView.layoutManager's methods.
invalidateDisplayCharacterRange:
imageForBounds:textContainer:characterIndex: will be called again.
attachmentBoundsForTextContainer:[...]Index: will not be called again.
Good if the image has been changed with another one of the same size.
invalidateLayoutForCharacterRange:actualCharacterRange:
imageForBounds:textContainer:characterIndex: will be called again.
attachmentBoundsForTextContainer:[...]Index: will be called again.
Good if the image has been changed with another one of a different size.
If you just want to update a single attachment, you may find this helper method I wrote helpful:
- (NSRange)rangeOfAttachment:(NSTextAttachment *)attachment {
__block NSRange ret;
[self.textStorage enumerateAttribute:NSAttachmentAttributeName
inRange:NSMakeRange(0, self.textStorage.length)
options:0
usingBlock:^(id value, NSRange range, BOOL *stop) {
if (attachment == value) {
ret = range;
*stop = YES;
}
}];
return ret;
}
You can pass the resulting NSRange of this method to the first argument of either of those invalidate methods. For the actualCharacterRange: argument of the second method, I've been passing in NULL without any problems.
Well, after some digging, that can done by invalidating the layout manager:
[textView.layoutManager invalidate....]
Apple docs
If you just want to change the NSTextAttachment which is init by a image, I suggest you to use
setAttachmentSize:size forGlyphRange:range

Resources