I have been re-writing my app for iOS 7. It now uses the new TextKit API. I'm nearly complete but there is one more issue that has been causing me headache since I've started doing the transition.
Problem: When I enter text in the UITextView, on occasion, the view will scroll up hiding the cursor. It's difficult to pin-point the pattern but it appears to happen after I delete a line and the text below has to move up. But only the SECOND time I remove a line and ONLY with certain lines in the document (generally lines that have only a newline character but not always...)
I have tried using these solutions on stackoverflow:
How to stop UITextView from scrolling up when entering it
UITextView keep scrolling after every new line is inserted, so the lines are invisible (iPhone OS)
Scroll to bottom of UITextView erratic in iOS 7
There are also many other issues where scrolling does not work correctly when having a UITableView within a UITextView. I have NO table views in my view.
I want to point out that I have subclassed UITextView and discovered that it is definitely iOS 7 making the calls to not only scroll the view upwards. Also, what's even stranger, is that when you press the back button (to delete a character from the UITextView) it sends two messages to textViewDidChangeSelection:
. Here is the call stack when the issue occurs. (When the issue doesn't occur I still get the two calls to delegate textViewDidChangeSelection:
... which appears to be normal):
- UITextView delegate
textViewDidChangeSelection:
contains the position of the character to delete with a length of one. (ie. 1050,1) - UITextView delegate
textViewDidChangeSelection:
is called again and contains the same position but with a length of zero. (ie. 1050,0) - scrollViewDidScroll (up to 3 times. but generally only once)
- Then iOS 7 calls
UITextView.scrollRangeToVisible:
w/ the FIRST call's range of {1050,1}. This makes no sense to me. I know this is being called internally, and NOT from any of my code, because this internal API is called_ensureselectionvisible
w/o any traceback to a function I may have used to invoke scroll event.
Presumably the first two calls are simply to select the character, and then, internally, calls deleteBackwards. That makes sense. I'm flying blind after that though. I have no idea why it scrolls or why it calls scrollRangeToVisible:
w/ the WRONG range.
I have been able to mitigate the issue somewhat by over-riding UITextView.scrollRangeToVisible:
by returning w/o calling [super scrollRangeToVisible]
when the user is editing text. I do this by over-riding some of the scrollview delegate calls and setting a flag that says scrolling can't occur. This, to me, is a huge UGLY hack and the issue still occurs in certain situations -- such as when I tap into the view when first editing and sometimes when the scroll view stops decelerating.
In short, what's happening is that a scrolling event occurs (probably due to the previous line moving up) and iOS for some reason thinks that the cursor isn't in the view. What's even more curious is that, even though the wrong range is provided, it does provide the correct position, which in turn scrolls it to the wrong location in the view. What's even more curious is that it only happens in certain conditions and only the second instance where the condition occurs. The only thing I can think of is that the height of the UITextView is not computed correctly after the text is removed and it believes the text is in a different location than it actually is.
Thanks in advance!
UPDATE:
I set the flag I spoke of earlier in scrollViewDidScroll:
and it doesn't prevent the jumping in some other instances. However, there are still instances where deleting text backwards still jumps the screen. What's even more strange in the instance where the view still jumps is that I see that I am preventing [super scrollRangeToVisible]
from being called! So there's something happening internally, in ADDITION, to this which is causing the view to scroll.
UPDATE:
It appears to jump between 613pts and 670pts upwards. I'm not sure what causes the difference in points. The number of lines it jumps varies as well. I would suspect that something has to be similar between the conditions. Also, when the scrollViewDidScroll:
gets called I verified that the textView.selectedRange.location
is the same between this call and the delegate textViewDidChangeSelection:
call.