Unscrollable UITextView in UITableViewCell - How to determine cursor y coordinate
I'm using a UITextView inside a UITableVie开发者_Python百科w cell. I have scrolling disabled for the UITextView. When the user types, I have logic to auto grow both the UITextView and UITableView cell. I also have logic to auto scroll the UITableView and keep the newly typed text visible. I do this by calling [tableView scrollRectToVisible:animated:] This all works fine, as long as the cursor is at the very end of the text, since I will simply scroll to the end (tableView.contentSize.height).
The problem comes, when the user decides to position the cursor elsewhere in the text (say in the middle of their block), and begin typing again. At this point, I can no longer keep the newly typed text in view, because I can't determine what y coordinate the cursor is at.
Any help would be greatly appreciated!
OK, I finally implemented it and there is much easier, you don't need to use contentOffset as I said before, It works but has some unexpected problems. The trick is using the auxiliar UITextView
for calculating the height of the text above the cursor.
Here is the idea (I tested it and works):
Create an auxiliar
UITextView
(_auxTextView)Implement the
textViewDidChange
delegate for the main text view.
The table view controller should implement cellDidChange
by scrolling the table view using scrollRectToVisible
with the given offset. The exact calculations deppend on the case but the important thing here is calling beginUpdate
and endUpdate
in order to force updating the row height that should be based in the property rowHeight.
Here is part of the code (sorry, I can't post the actual code because is inside a big context).
-(void)textViewDidChange:(UITextView *)textView {
if(_prevHeight != self.textHeight) {
_prevHeight = self.textHeight;
NSRange range;
range.location = 0;
range.length = textView.selectedRange.location;
NSString *str = [_textView.text substringWithRange:range];
_auxTextView.text = str;
[delegate cellDidChange:self offset:_auxTextView.contentSize.height];
}
}
-(CGFloat)rowHeight {
return self.textHeight + somePadding;
}
-(CGFloat)textHeight {
return fmaxf(_textView.contentSize.height, someMinimumHeight);
}
I have managed to solved this sort of problem (at least for most cases), but it can be tricky. Getting the y co-ordinate is not so hard. Suppressing stray scroll messages that the system fires at your table can be.
The approach I have taken goes like this:
- in the delegate for your UITextView implement textViewDidChangeSelection:
- get the text from the textView and the selectedRange
- you may need to check that the selected range is not > text length (sometimes it can be; if you believe it couldn't possible, you may get weird crashes... I did)
- truncate the string, so you get the string from the start to the selection point
calculate the height of a UITextView for a string of that length. Use something like:
textHeight = [workingString sizeWithFont: [self screenFont] constrainedToSize: CGSizeMake([self editWidth]-16.0, CGFLOAT_MAX)].height;
the -16.0 is important to compensate for the default border. I've had good success with 16.0, but it's not documented. There may be a better way to figure out the compensation, but I've not worked it out.
- at this point I manually scroll the tableView. It is a subclass of UIScrollView, so responds to setContentOffset: However, to get everything to work smoothly, I sub classed UITableView and got it to sometimes ignore contentOffset: and contentOffset:animated: messages. The system generates these and sometimes it does when you don't want them. (That's what i mean above by 'tricky').
There may be a much simpler way to achieve this. I hope so. And if so, that someone posts it.
Here's a bit of (slightly ugly) code from my app (still in work in progress). It uses some other classes / categories from my app, but hopefully you can follow what's going on readily enough. This might not quite match your issue, but hopefully this is of some use.
- (void) textViewDidChangeSelection: (UITextView *)textView;
{
if (!checkSelectionVisible) return; // bail most of the time
checkSelectionVisible = NO; // this is a one off
EditController* theEditController = [self editController];
ParagraftTableView* theTableView = (ParagraftTableView*)[theEditController tableView];
ParagraphCell* currentCell = (ParagraphCell*) [theTableView cellForRowAtIndexPath: [NSIndexPath indexPathForRow:[self paragraphNumber] inSection:0]];
CGPoint cellOrigin = currentCell.origin;
CGPoint contentOffset = [theTableView contentOffset];
NSRange selectedRange = [textView selectedRange];
NSString* text = [textView text];
if (selectedRange.location > [text length])
{
selectedRange.location = 0; // of out of bounds set to start
NSLog(@"In textViewDidChangeSelection - selection out of bounds. Setting to start of paragraph.");
}
CGFloat textHeight = [[Formats shared] heightForText: [text substringToIndex: selectedRange.location]];
if ((cellOrigin.y + textHeight) > ([theTableView height]+ [theTableView contentOffset].y))
{
NSLog(@"Selection point is off screen. Scroll into view.");
CGFloat txHeight = [[[Formats shared] screenFont] pointSize];
CGFloat newOffset = cellOrigin.y + textHeight - ([theTableView height] - 3*txHeight);
contentOffset.y = newOffset;
[theTableView setContentOffset:contentOffset];
}
}
There is actually a simpler way to do it :)
Just add a hidden UITextView and mirror all the changes made to the main text view in the hidden one. The hidden text view should have a fixed height, so it will scroll automatically changing the contentOffset property. And contentOffset is exactly what you need in order to calculate the vertical position of the cursor.
Goog luck!
精彩评论