I'm using Text Kit in combination with UIPageViewController to create a book. The original text is stored in a text file (.html) and placed into a NSTextStorage.
The [NSLayoutManager addTextContainer:(NSTextContaner*)txt] method works great, but there is no way to know when the NSLayoutManager has filled the last NSTextContainer - it just keep returning NSTextContainer even after all the next is displayed. As result you get an all bank pages after the NSLayoutManager is done.
I've tried using the [NSLayoutManagerDelegate didCompleteLayoutForTextContainer: atEnd:] callback method but it isn't working properly. It returns atEnd flag = YES after filling each NSTextContainer, not just when the last NSTextContainer is filled. I have set UITextView.scrollable = NO (suggested elsewhere) but that doesn't help.
I also tried to check the text by calling UITextView.text when no text is displayed, but that method always return the contents of the entire NSTextStorage that lays behind the NSTextContainer/NSLayoutManager.
If I can't tell when the last container is filled I don't know when all the pages are laid out. Is there a way to test the UITextView to see if its empty or NSContainer, or NSLayoutManager to see done laying out text?
I was never able to get the call back method be called correctly - I think its a bug - but I found a way to check if the next container is going to be empty or not..
[_layoutManager addTextContainer:textContainer];
NSRange range = [_layoutManager glyphRangeForTextContainer:textContainer];
if (range.length == 0) {
return nil;// top adding container because this last one is empty
}
Check whether container is last by calling glyphRangeForTextContainer method. If it exceeds number of glyphs, that's the last container.
layoutManager.addTextContainer(container)
let glyphRange = NSMaxRange((layoutManager.glyphRangeForTextContainer(container)))
if glyphRange >= layoutManager.numberOfGlyphs {
// last container
}
Related
I have a UITextView with some text that exceeds its bounds. Thus, vertical scrolling is enabled.
Now I want to find the position of the first visible character. This is what I tried:
let firstCharacterPosition = characterRange(at: contentOffset)?.start
This works in most cases. However, when I scroll up and get close to the beginning of the text, the characterRange(at:) function suddenly returns nil.
In the beginning I thought it was only because of bouncing (when the contentOffset.y value shortly becomes < 0). But that's not the (only) reason.
I tried some other values and was surprised to find that
characterRange(at: .zero)
returns nil as well – just as text positions with a low positive y value.
Why is that?
How can I get a reliable UITextPosition for the first visible character?
Please, try this code (remove textView if you are calling it inside UITextView subclass):
let firstVisibleCharacterIndex = textView.layoutManager.characterIndex(for: textView.contentOffset, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
In the Swift app I'm creating, the user can type some text into a text view and then search for a specific string within it, just like in Pages (and numerous others).
I have the index of the character they are searching for as an Integer (i.e., they are on the third instance of "hello" in the massive text block they typed in, and I know that's the 779th letter of the text view) and I am trying to automatically scroll the text view to the string they're on, so that it pops out (like it would in Pages).
I am trying to jump to the applicable string with this command:
self.textview.scrollRangeToVisible(NSMakeRange(index, 0))
but it never jumps to the right place (sometimes too far down, sometimes way too far up), often depending on screen size, so I know that index doesn't belong right there.
How can I correctly allow the user to jump to a specific string in a text view, by making the text view scroll to a certain character?
You can get string, which is before index 779, and then calculate the height of this string and then scroll to this point.
let string = textView.text.substringWithRange(Range<String.Index>(start: textView.text.startIndex, end: textView.text.startIndex.advancedBy(779)))// your <779 string
let size = string.sizeWithAttributes([NSFontAttributeName:yourFont])
let point = CGPointMake(0, size.height)
scrollView.setContentOffset(point, animated:true)
The other answer doesn't seem to work anymore (Jan 2021). But there is a simpler way now:
// Range specific to the question, any other works
let rangeStart = textView.text.startIndex
let rangeEnd = textView.text.index(rangeStart, offsetBy: 779)
let range = rangeStart..<rangeEnd
// Convert to NSRange
let nsrange = NSRange(range, in: textView.text)
// ... and scroll
textView.scrollRangeToVisible(nsrange)
I have been having this problem with a few of my UITextViews on my most recent project. All I have done is added a UITextView onto a ViewController and added some constraints to it. When I run my program and attempted to type in the UITextView it begins to bounce out of control. For instance, when I type the first character, the view will slide down and out of the screen. Then when I type the next few characters the textview will bounce back up to where I can see what I typed. Then I type a few more, and once again it bounces out of the screen. Has anyone else experienced this?
Also, sometimes the text will start in the middle of the UITextView.
Try add this code to textViewDidChange: method
func textViewDidChange(textView: UITextView) {
if textView.text?.characters.count ?? 0 > 0 {
let range = NSMakeRange(textView.text!.characters.count - 1, 1)
textView.scrollRangeToVisible(range);
}
}
It helped me.
My app includes a text formatting tool that offers buttons for things like bold, italic and color and shows the formatted text by generating an NSAttributedString and setting that to the attributedText property of a UITextView. After the user selects text and taps a button, I get the selectedRange property of the UITextView, then get the current attributedText property of the UITextView, add another attribute to the text based on the selected range, and then assign it back to the attributedText property of the UITextView again.
Starting with iOS 7, my text formatting started displaying at the wrong location in the text, usually shifted a couple characters forward. After some testing I noticed that this only happened after an empty line (e.g., a paragraph of text with two line breaks after it) and the formatting was offset by one character for each empty line proceeding it.
After more testing I found that when I set the attributedText property for the first time, any sequence of two line breaks is changed to a line break, then a "line separator" character (Unicode 8232) and then the second line break. The new characters are definitely added by the attributedText assignment, as I can see from outputting the integer value of each character immediately before and immediately after that action. However, the selectedRange property of the UITextView ignores the line separator characters, so any range that it returns is now incorrect.
I've already found a workaround, which I'll add as an answer in a moment. I'm mainly posting this in case anyone else is having problems with it. Also, I've reported this to Apple as bug 15349335.
I wrote this method to adjust ranges returned by the selectedRange property to account for these extra line separator characters:
- (NSRange)adjustRangeForEmptyLines:(NSRange)range inText:(NSAttributedString *)text byChars:(int)chars {
int emptyLinesBeforeRange = 0;
int emptyLinesWithinRange = 0;
for (int i=0; i<(range.location + range.length); i++) {
int thisCharacter = [text.string characterAtIndex:i];
//NSLog(#"thisCharacter: %i", thisCharacter);
if (thisCharacter == 8232) {
if (i < range.location) {
emptyLinesBeforeRange++;
} else {
emptyLinesWithinRange++;
}
}
}
//NSLog(#"found %i + %i empty lines", emptyLinesBeforeRange, emptyLinesWithinRange);
range.location += (emptyLinesBeforeRange * chars);
range.length += (emptyLinesWithinRange * chars);
return range;
}
I can set the byChars argument to 1 or -1 depending on which way I want to adjust. This has been working for me for a few weeks now, but if anyone has an alternate solution, I'd be curious to see it.
I have a UITextView that has a fixed width and height. I pre-populate the entire textfield with blanks.
I would like to insert a character with the push of a button that will erase the last blank character, insert my string character and then place the cursor at the beginning of the newly inserted string. I am trying to achieve inserting special fonts right to left and bottom to top.
It is working with the first button push and on the second button push the new value is inserted in the correct position to the left, however, the cursor will not move to the left after the second button push, it remains to the right after the second string insert.
Here is my code...
-(IBAction)chartP:(id)sender {
NSRange currentRange = myChart.selectedRange;
if (currentRange.length == 0) {
currentRange.location--;
currentRange.length++;
}
myChart.text = [myChart.text stringByReplacingCharactersInRange:currentRange
withString:[NSString string]];
currentRange.length = 0;
myChart.selectedRange = currentRange;
myChart.text = [myChart.text stringByAppendingString:#"p"];
myChart.selectedRange = NSMakeRange(myChart.selectedRange.location -1, 0);
}
Can someone assist me with what I am missing here to continually increment to the left with my string inserts?
How about flipping the text area:
myChart.transform = CGAffineTransformMakeScale(-1,-1);
It sounds like you are trying to implement right-to-left text direction by faking it. Why not do the real thing?
Here's a question that covers the topic:
Change the UITextView Text Direction
If you need bottom-to-top entry, and you have the ability to use a custom font, perhaps you can apply a transformation to the UITextView and y-flip it. Look at the transform property of UIView. (Things like the text selection loupe may break, but it's worth a try.)