How to clear a UITextView with a custom NSTextStorage? [iOS7 only] - ios

I'm currently trying to create an SMS-like screen, where the user can write some text and send it to other users. Everything goes as expected until I try to clear my text view and encounters a crash.
I've been trying to find a way around this issue, but I just cannot find enough documentation online. So here it is, and hopefully one of you will know a fix for this.
The implementation
My UITextView is a subclass of Peter Steinberger's implementation for iOS7, and I use it with a custom NSTextStorage subclassed as showed in objc.io's 'Getting to Know TextKit' and especially that source code in order to highlight usernames in the message.
In my ViewController, I configure my text storage like this:
self.textStorage = [[[MyCustomTextStorage alloc] init] autorelease];
[self.textStorage addLayoutManager:self.textView.layoutManager];
And then in my TextView's delegate method:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
I store the input in my custom TextStorage:
[self.textStorage.string stringByReplacingCharactersInRange:range withString:text];
The crash
I can retrieve my text via self.textStorage.string, and then I clear my text view by replacing characters in the range of the input string. This works quite well, but when I try to set my TextView as the first responder again, the application crashes.
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSCFString _getBlockStart:end:contentsEnd:forRange:stopAtLineSeparators:]: Range {5, 0} out of bounds; string length 0'
The range mentioned is the range of my previously cleared string so it looks like I can clear the display, but my TextStorage / TextView is keeping a reference to the first edited range / string.
Any idea on what could be causing this crash and how to resolve it?
Thanks for your attention; at this point any help is appreciated, so please feel free to post if you have some piece of advice. :)

The reason it's crashing is because you are using a range that it outside the bounds of the string in the textStorage. If you want to clear the textView why not just use self.textStorage.string=#""

Same situation with you.
It costed me half a day to solve it.
The example you provide is right.
Your app will crash not only when you are clearing it, but also when using -setText: every time.
They don't crash beacause they would never call -setText:!!!
Solution:
add all TextKit staffs you need to your code,
like this:
_textStorage = [[NSTextStorage alloc] init];
_layoutManager = [[NSLayoutManager alloc] init];
[_textStorage addLayoutManager:_layoutManager];
_textContainer = [[NSTextContainer alloc] init];
[_layoutManager addTextContainer:_textContainer];
_myTextView = [[UITextView alloc] initWithFrame:frame textContainer:_textContainer];
Actually, you missed the NSTextContainer, so there's no right container for string to put in, then the app crashed.
Maybe you can send pull requests to these two demo project, for not miss leading other people.

After replacing text, try:
[self setSelectedRange:NSMakeRange(0, 0)];

Related

When tapping a custom URL link in a UITextView the delegate receives nil URL

I have a tableView with tableViewCells cell that show a textView.
textView uses an attributedString with custom URL link information, set up in tableView:cellForRowAtIndexPath: as shown in this tutorial:
NSMutableAttributedString *attributedDisplayString = [[NSMutableAttributedString alloc] initWithString:displayString];
[attributedDisplayString addAttribute:NSLinkAttributeName
value:[NSString stringWithFormat:#"username://%#", userName]
range:NSMakeRange(0, userName.length)];
cell.textView.attributedText = attributedDisplayString;
When I tap the link, textView:shouldInteractWithURL:inRange: is called in the delegate, thus the custom URL link has been detected and responds.
However, the supplied URL is nil.
What am I missing?
Sorry for asking too fast. I found the problem, but maybe this helps others:
My variable userName simply contained a space, and could thus not be converted to a URL.
After removing the space, it works.
To make a string that can be used for a URL, one can use in iOS8 and earlier
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding
and in iOS9
stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]

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.

NSTextContainer exclusionPaths freezes app and uses 99% CPU on iOS 7.1 - workaround?

I'm trying to exclude a square in a UITextView using NSTextContainer's excludePaths, like so:
NSTextStorage* textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];
NSLayoutManager *layoutManager = [NSLayoutManager new];
[textStorage addLayoutManager:layoutManager];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:self.bounds.size];
UIBezierPath *rectanglePath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 250, 250)];
textContainer.exclusionPaths = #[rectanglePath];
[layoutManager addTextContainer:textContainer];
self.textView = [[UITextView alloc] initWithFrame:self.bounds textContainer:textContainer];
self.textView.editable = NO;
self.textView.scrollEnabled = NO;
[self addSubview:self.textView];
This works fine in iOS 7.0:
In iOS 7.1, however, this will result in an infinite loop somewhere in lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect: of NSTextContainer, using 99% CPU and leaking memory like crazy. The app is completely unresponsive and is eventually terminated due to memory use. Apparently this is a bug in iOS 7.1.
When I change the x-origin of the exclusion rectangle by just one point (origin to {1,0}), it works, but looks terrible:
The bug only seems to happen when the first character of the first line is affected by the exclusion rect. When I change the exclusion rect to {0,30}, it will also work:
But obviously this is not what I want. Does anyone know how I can work around this bug?
I have the same issue, to fix this i placed:
mytextView.exclusionPaths = #[rectanglePath]
into layoutSubview method.
I hope this will help someone
Actually I encountered the same thing with iOS 7 and an attributed Text.
I had to completely remove the attributed text, make the UITextView selectable so I can change text color and font and only then it worked.
Sigh.
Just mentioning this in case anyone stumbles upon this in the future.

UITextView crash when clicking on a URL

I have the following lines in my code to add a clickable url to a UITextView. merchantwebsite is my UITextView.
self.merchantwebsite.attributedText = [[NSAttributedString alloc] initWithString:#"http://www.crossfit.com" attributes:#{NSLinkAttributeName: #"http://www.crossfit.com"}];
self.merchantwebsite.userInteractionEnabled = YES;
When I click on the UITextView, the app crashes with the log
2014-03-19 16:13:43.051 BTLE[27103:60b] -[__NSCFConstantString scheme]: unrecognized selector sent to instance 0x1a4404
Can someone please tell me what i'm doing wrong.
Thanks!
You evidently have code that expects the URL to be an NSURL. But, quite simply, an NSString is not an NSURL. Try it like this:
self.tv.attributedText =
[[NSAttributedString alloc]
initWithString:#"http://www.crossfit.com"
attributes:
#{NSLinkAttributeName: [NSURL URLWithString:#"http://www.crossfit.com"]}];
Just type the link in when you are telling the UITextView what to display and then turn on link recognition in the settings on the right when your UITextView is selected from the .storyboard page.
You don't really need any of the code that you have all you need is:
merchantwebsite.text = #"http://www.crossfit.com";
Change your code as follows:
NSAttributedString *string = [[NSAttributedString alloc] initWithString:#"http://www.crossfit.com" attributes:#{NSLinkAttributeName: #"http://www.crossfit.com"}];
[self.merchantwebsite setAttributedText:string];

UITextView changes font when detects action

I've been looking for a solution to this problem for a while, and no one seems to have come across a similar issue.
Basically I have multiple UITextViews that I use to detect addresses, urls, phone numbers, etc (anything that can be detected via UIDataDectorTypeAll) from some EKEvent.notes. I then add these UITextViews as subviews of a UIScrollView.
Now, for some reason or another, once the UITextView detects an address or a phone number and becomes an actionable target, it will randomly draw with a font 2x its specified font!
I've setup tests to just redraw my views if I tap. When the UITextView is added to the view initially, I can see in black the proper text. Then it does its detection deal and becomes a actionable target. Sometimes it stays the proper size, sometimes it draws at 2x font (but still in proper frame, thus it gets clipped).
It's very straight forward, but here's my code below. All variable are correct values, frame is correct, text is correct, everything is correct and about 50% of the time it draws correct. Its just that other 50% of the time it becomes (apparently) 2x font! Any help is greatly appreciated!
UITextView *locationTextView = [[UITextView alloc] init];
locationTextView.dataDetectorTypes = UIDataDetectorTypeAll;
locationTextView.text = location;
locationTextView.font = [UIFont fontWithName:#"AvenirNext-Regular" size:17];
locationTextView.editable = NO;
locationTextView.userInteractionEnabled = YES;
locationTextView.contentInset = UIEdgeInsetsMake(-8,-8,-8,-8);
locationTextView.frame =CGRectMake(kBufferLeft, daySize.height, kBufferDayViewTextWidth, locationSize.height);
[scrollView addSubview:locationTextView];
Correct: http://i.imgur.com/3pJ43kj.jpg
Incorrect: http://i.imgur.com/DLq4gco.jpg
(Not allowed to post images yet, sorry.)
Same exact code produced both effect. Thank you for your time.
Cheers!
EDIT: I went with TTTAttributedLabels to fix this issue.
github.com/mattt/TTTAttributedLabel
You can set font at <UITextField> delegate.
-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
locationTextView.font = [UIFont fontWithName:#"AvenirNext-Regular" size:17];
}
I had the same problem because I was using a custom line breaking (layoutManager:shouldBreakLineByWordBeforeCharacterAtIndex:). Had to disable that.

Resources