UITextView cursor below frame when changing frame
Instead of resizing the frame, why not give your text view a contentInset
(and a matching scrollIndicatorInsets
)? Remember that text views are actually scrollviews. This is the correct way to handle keyboard (or other) interference.
For more information on contentInset
, see this question.
This seems to not be enough. Still use insets, as this is more correct (especially on iOS7, where the keyboard is transparent), but you will also need extra handling for the caret:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.textView setDelegate:self];
self.textView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_keyboardWillShowNotification:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_keyboardWillHideNotification:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)_keyboardWillShowNotification:(NSNotification*)notification
{
UIEdgeInsets insets = self.textView.contentInset;
insets.bottom += [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
self.textView.contentInset = insets;
insets = self.textView.scrollIndicatorInsets;
insets.bottom += [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
self.textView.scrollIndicatorInsets = insets;
}
- (void)_keyboardWillHideNotification:(NSNotification*)notification
{
UIEdgeInsets insets = self.textView.contentInset;
insets.bottom -= [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.height;
self.textView.contentInset = insets;
insets = self.textView.scrollIndicatorInsets;
insets.bottom -= [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.height;
self.textView.scrollIndicatorInsets = insets;
}
- (void)textViewDidBeginEditing:(UITextView *)textView
{
_oldRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end];
_caretVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(_scrollCaretToVisible) userInfo:nil repeats:YES];
}
- (void)textViewDidEndEditing:(UITextView *)textView
{
[_caretVisibilityTimer invalidate];
_caretVisibilityTimer = nil;
}
- (void)_scrollCaretToVisible
{
//This is where the cursor is at.
CGRect caretRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end];
if(CGRectEqualToRect(caretRect, _oldRect))
return;
_oldRect = caretRect;
//This is the visible rect of the textview.
CGRect visibleRect = self.textView.bounds;
visibleRect.size.height -= (self.textView.contentInset.top + self.textView.contentInset.bottom);
visibleRect.origin.y = self.textView.contentOffset.y;
//We will scroll only if the caret falls outside of the visible rect.
if(!CGRectContainsRect(visibleRect, caretRect))
{
CGPoint newOffset = self.textView.contentOffset;
newOffset.y = MAX((caretRect.origin.y + caretRect.size.height) - visibleRect.size.height + 5, 0);
[self.textView setContentOffset:newOffset animated:YES];
}
}
-(void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
A lot of work, Apple should provide better way of handling the caret, but this works.
iOS8 UITextView cursor below frame when changing frame with Animation
If anyone is interested I have found that for iOS8 I needed to override scrollRectToVisible which is the equivalent of overriding scrollRangeToVisible in iOS7
iOS7
- (void) scrollRangeToVisible:(NSRange)range { }
iOS8
- (void) scrollRectToVisible:(CGRect)rect animated:(BOOL)animated{ [self scrollCaretToVisible]; }
Cursor outside UITextView
This appears to be a bug in iOS 7. The only way I've found to correct this issue is to add a delegate for the UITextView and implement textViewDidChangeSelection
, resetting the view to show the selection like this:
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
- (void) textViewDidChangeSelection: (UITextView *) tView {
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) {
[tView scrollRangeToVisible:[tView selectedRange]];
}
}
Why cursor doesn't reach till end of frame in UITextView
This is how the UITextView has been designed. It's basically the same logic as a typewriter. You see white space below all the time. If you want to remove that space, there's a @property that could help you remove that:
-setContentInset:(UIEdgeInset)inset
Remember that UITextView is a subclass of UIScrollView. There's a lot of customization you can achieve by changing settings.
UITextView cursor not positioning properly when editing in iOS 7. Why?
this bug is in iOS 7.0 you can solve this by modifying textView delegate method.
try below code
- (void)textViewDidChange:(UITextView *)textView {
CGRect line = [textView caretRectForPosition:
textView.selectedTextRange.start];
CGFloat overflow = line.origin.y + line.size.height
- ( textView.contentOffset.y + textView.bounds.size.height
- textView.contentInset.bottom - textView.contentInset.top );
if ( overflow > 0 ) {
// We are at the bottom of the visible text and introduced a line feed, scroll down (iOS 7 does not do it)
// Scroll caret to visible area
CGPoint offset = textView.contentOffset;
offset.y += overflow + 7; // leave 7 pixels margin
// Cannot animate with setContentOffset:animated: or caret will not appear
[UIView animateWithDuration:.2 animations:^{
[textView setContentOffset:offset];
}];
}
}
your problem will solved.
UITextView content offset changes after setting frame
Setting textView.scrollable = NO
lets me resize the text view without any strange offsets, that's the only way I've been able to figure out. And I guess it's not too much of a limitation for common scenarios, if you want the text view to be scrollable you probably don't need to resize it on the fly since the user can scroll around as the content changes.
How to set cursor position for UITextView on user input?
So I ended up adding a UILabel over the UITextView
which acts as a placeholder for the textView. Tapping on the UILabel would send the action down to the textView and becomeFirstResponder
. Once you start typing, make the label hidden.
Getting and Setting Cursor Position of UITextField and UITextView in Swift
The following content applies to both UITextField
and UITextView
.
Useful information
The very beginning of the text field text:
let startPosition: UITextPosition = textField.beginningOfDocument
The very end of the text field text:
let endPosition: UITextPosition = textField.endOfDocument
The currently selected range:
let selectedRange: UITextRange? = textField.selectedTextRange
Get cursor position
if let selectedRange = textField.selectedTextRange {
let cursorPosition = textField.offset(from: textField.beginningOfDocument, to: selectedRange.start)
print("\(cursorPosition)")
}
Set cursor position
In order to set the position, all of these methods are actually setting a range with the same start and end values.
To the beginning
let newPosition = textField.beginningOfDocument
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
To the end
let newPosition = textField.endOfDocument
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
To one position to the left of the current cursor position
// only if there is a currently selected range
if let selectedRange = textField.selectedTextRange {
// and only if the new position is valid
if let newPosition = textField.position(from: selectedRange.start, offset: -1) {
// set the new position
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
}
}
To an arbitrary position
Start at the beginning and move 5 characters to the right.
let arbitraryValue: Int = 5
if let newPosition = textField.position(from: textField.beginningOfDocument, offset: arbitraryValue) {
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
}
Related
Select all text
textField.selectedTextRange = textField.textRange(from: textField.beginningOfDocument, to: textField.endOfDocument)
Select a range of text
// Range: 3 to 7
let startPosition = textField.position(from: textField.beginningOfDocument, offset: 3)
let endPosition = textField.position(from: textField.beginningOfDocument, offset: 7)
if startPosition != nil && endPosition != nil {
textField.selectedTextRange = textField.textRange(from: startPosition!, to: endPosition!)
}
Insert text at the current cursor position
textField.insertText("Hello")
Notes
Use
textField.becomeFirstResponder()
to give focus to the text field and make the keyboard appear.See this answer for how to get the text at some range.
See also
- How to Create a Range in Swift
Related Topics
What Is the Bundle Identifier of Apple's Default Applications in iOS
Error Itms-90164/90046: Invalid Code Signing Entitlements
When Does a Uitableview's Contentsize Get Set
Convert Mkcoordinateregion to Mkmaprect
Swift Merge Audio and Video Files into One Video
Simulator Slow-Motion Animations Are Now On
How to Save Nsmutablearray or Nsdictionary Data as File in iOS
iPhone - When to Calculate Heightforrowatindexpath for a Tableview When Each Cell Height Is Dynamic
Get Current iPhone Device Timezone Date and Time from Utc-5 Timezone Date and Time iPhone App
How to Write an Objective-C Completion Block
Upload Files to Dropbox from iOS App with Swift
How to Set Imageview in Circle Like Imagecontacts in Swift Correctly
Dateformatter Returns Unexpected Date for Timezone
Completion Handler for Uinavigationcontroller "Pushviewcontroller:Animated"