I have a UISearchBar on which I am trying to set a cursor position. I am using UITectField delegates as I couldn't find anything direct for UISearchBar. Below is the code I am using:
UITextField *textField = [searchBar valueForKey: #"_searchField"];
// Get current selected range , this example assumes is an insertion point or empty selection
UITextRange *selectedRange = [textField selectedTextRange];
// Construct a new range using the object that adopts the UITextInput, our textfield
UITextRange *newRange = [textField textRangeFromPosition:selectedRange.start toPosition:selectedRange.end];
Question is in the 'newRange' object for 'toPosition' I want to have something like selectedRange.end-1; as I want the cursor to be on second last position.
How do I set the cursor to second last position?
Swift 5
I came across this question originally because I was wondering how to convert UITextPosition to an Int (based on your title). So that is what I will answer here.
You can get an Int for the current text position like this:
if let selectedRange = textField.selectedTextRange {
// cursorPosition is an Int
let cursorPosition = textField.offset(from: textField.beginningOfDocument, to: selectedRange.start)
}
Note: The properties and functions used above are available on types that implement the UITextInput protocol so if textView was a UITextView object, you could replace the instances of textField with textView and it would work similarly.
For setting the cursor position and other related tasks, see my fuller answer.
the clue is to make a position and then a range with no length and then select it
e.g.
- (IBAction)select:(id)sender {
//get position: go from end 1 to the left
UITextPosition *pos = [_textField positionFromPosition:_textField.endOfDocument
inDirection:UITextLayoutDirectionLeft
offset:1];
//make a 0 length range at position
UITextRange *newRange = [_textField textRangeFromPosition:pos
toPosition:pos];
//select it to move cursor
_textField.selectedTextRange = newRange;
}
Related
I am displaying an array of strings separated by \n or linebreaks in a textview. Thanks to the \n, if there is room in the textview, each string gets its own line. However, if the width of the textview is less than the string, then the textview automatically wraps the line to the next line so that the string in effect takes two lines. Nothing wrong with this so far.
However, I want to grab the string if someone touches it. It works fine if the string fits on a line. However, if the string has been broken across two lines by textview, if the user touches the top line, I need to attach the line below to get the whole string. Or if the user touches the bottom line, I need to get and add the previous one to have a whole string.
Can anyone suggest proper way to do this?
Here is my code that grabs the line the person touches, however, it only gets the line touched and fails when it tries to calculate the index of the string in the array.
Thanks for any suggestions:
- (void) handleTap:(UITapGestureRecognizer *)recognizer{
UITextView *textView = (UITextView *)recognizer.view;
CGPoint location = [recognizer locationInView:textView];
CGPoint position = CGPointMake(location.x, location.y);
UITextPosition *tapPosition = [textView closestPositionToPoint:position];
UITextRange *textRange = [textView.tokenizer rangeEnclosingPosition:tapPosition withGranularity:UITextGranularityLine inDirection:UITextLayoutDirectionRight];
//In following line, I am not getting the full text in the string, only the text of that line. I need to get the whole string.
NSString *tappedLine = [textView textInRange:textRange];
NSArray* myArray = [self.listSub.text componentsSeparatedByString:#"\n"];
NSInteger indexOfTheObject = [myArray indexOfObject: tappedLine];
//If the tapped line is not the same thing as the string, the above index becomes huge number like 94959494994949494 ie an error, not the index I want.
}
This is indeed possible.
First, you need to change the granularity to UITextGranularityParagraph:
UITextRange *textRange = [textView.tokenizer rangeEnclosingPosition:tapPosition withGranularity:UITextGranularityParagraph inDirection:UITextLayoutDirectionRight];
This will return you the entire wrapped line of text that the user tapped, no matter where they tapped it.
However, this text will include the trailing \n character that marks the end of the paragraph. You need to remove this before comparing the text to your array. Replace the last line of your code above with these two lines:
NSString *trimmedLine = [tappedLine stringByTrimmingCharactersInSet:
[NSCharacterSet newlineCharacterSet]];
NSInteger indexOfTheObject = [myArray indexOfObject: trimmedLine];
You may want to look into using a UITableView
https://developer.apple.com/documentation/uikit/uitableview
you could create a cell with a text view for each entry in the array and you will get delegate calls when a user interacts with any of the cells
When user click the passage in textView, I want to change the cursor to the linebreak, but when I change selectionRange is always failed.
I know the reason, but I must change the selectedRange in func: textViewDidChangeSelection:(UITextView*)textView
How can I change the selectedRange?
here is the code
-(void)textViewDidChangeSelection:(UITextView *)textView
// paragLocations: it contain all "\n" locations
NSArray* paragLocations = [self ParagraphLocationsWithText:textView Pattern:translatePragraphLinebreak];
// location :According to user selection,The nearest "\n" location
NSUInteger nearestLocation = [self ClickParagraphEndBreakLoctionWithSelectLocation:textView.selectedRange.location withParagLocations:paragLocations];
//Then
//1. here I change the textView.selectedRange
textView.selectedRange = NSMakeRange(nearestLocation, 0);
//2. here I change the cursorPosition
CGFloat cursorPosition = [self caretRectForPosition:textView.selectedTextRange.start].origin.y;
[textView setContentOffset:CGPointMake(0, cursorPosition) animated:YES];
// but In step 1 ,It changed textView.selectedRange,So this func will do it again,and then again,until the nearestLocation became the paragLocations.lastObject.
/*
so the question is how to break this Infinite loop ?
should I change selectedRange In this func?
I want to changed the selectedRange base on User select In textview
*/
sorry about my poor english.
You can try the following code, it works for me fine.
- (void)textViewDidChangeSelection:(UITextView *)textView {
UITextRange *selectRange = [textView selectedTextRange];
NSString *selectedText = [textView textInRange:selectRange];
}
I have a uitextview which become the first responder on viewload. but i want to change the position of the cursor of the uitextview at certain position in x for the first 2 line only.
Maybe you could use a UITextKit (a very good tutorial).
For example to have a rounded text you can use something like:
UIBezierPath* exclusionPath = [UIBezierPath bezierPathWithOvalInRect:yourTextView.bounds];
exclusionPath = [exclusionPath bezierPathByReversingPath];
yourTextView.textContainer.exclusionPaths = #[exclusionPath];
Have you tried following solutions ?
Controlling cursor position in a UITextField is complicated because so many abstractions are involved with input boxes and calculating positions. However, it's certainly possible. You can use the member function setSelectedTextRange:
[input setSelectedTextRange:[input textRangeFromPosition:start toPosition:end]];
Here's a function which takes a range and selects the texts in that range. If you just want to place the cursor at a certain index, just use a range with length 0:
+ (void)selectTextForInput:(UITextField *)input atRange:(NSRange)range {
UITextPosition *start = [input positionFromPosition:[input beginningOfDocument]
offset:range.location];
UITextPosition *end = [input positionFromPosition:start
offset:range.length];
[input setSelectedTextRange:[input textRangeFromPosition:start toPosition:end]];
}
For example, to place the cursor at idx in the UITextField input:
[Helpers selectTextForInput:input
atRange:NSMakeRange(idx, 0)];
Source: https://stackoverflow.com/a/11532718/914111
Have not tested due to some busy so please let us know wether it is working or not (May have issue in IOS8.0)
I want to add a string in the highlighted area in the textview, I mean by the highlighted area, where the blue line is located.
So once the user click on the button it adds a string where the "blue line" is located
I used stringByAppendingString but it adds the string after the word exists only
NSRange range = myTextView.selectedRange;
NSString * firstHalfString = [myTextView.text substringToIndex:range.location];
NSString * secondHalfString = [myTextView.text substringFromIndex: range.location];
myTextView.scrollEnabled = NO; // turn off scrolling
NSString * insertingString = [NSString stringWithFormat:#"your string value here"];
myTextView.text = [NSString stringWithFormat: #"%#%#%#",
firstHalfString,
insertingString,
secondHalfString];
range.location += [insertingString length];
myTextView.selectedRange = range;
myTextView.scrollEnabled = YES;
You need to use the selectedRange to find out where the text cursor is. Then use replaceCharactersInRange:withString: or insertString:atIndex: to insert the new text into the original text. Then update the text into the view.
Even though its not clear what you are trying to achieve, it seems that you want the user to start editing the textfield from the position where text starts. In that case , you can refer following:
Hint 1
Set your view controller (or some other appropriate object) as the text field's delegate and implement the textFieldDidBeginEditing: method like this:
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
UITextPosition *beginning = [textField beginningOfDocument];
[textField setSelectedTextRange:[textField textRangeFromPosition:beginning
toPosition:beginning]];
}
Note that setSelectedTextRange: is a protocol method of UITextInput (which UITextField implements), so you won't find it directly in the UITextField documentation.
Hint 2
self.selectedTextRange = [self textRangeFromPosition:newPos toPosition:newPos];
Hint 3
finding-the-cursor-position-in-a-uitextfield/
can anybody help me with this: i need to implement UITextField for input number. This number should always be in decimal format with 4 places e.g. 12.3456 or 12.3400.
So I created NSNumberFormatter that helps me with decimal places.
I am setting the UITextField value in
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
method.
I proccess input, use formatter and finally call [textField setText: myFormattedValue];
This works fine but this call also moves the cursor to the end of my field. That is unwanted. E.g. I have 12.3400 in my field and the cursor is located on the very beginning and user types number 1. The result value is 112.3400 but cursor is moved at the end. I want to end with cursor when the user expects (just after the number 1 recently added). There are some topics about setting cursor in TextView but this is UITextField. I also tried to catch selectedTextRange of the field, which saves the cursor position properly but after setText method call, this automatically changes and the origin UITextRange is lost (changed to current). hopefully my explanation is clear.
Please, help me with this. thank you very much.
EDIT : Finally, i decided to switch the functionality to changing the format after whole editing and works good enough. I have done it by adding a selector forControlEvents:UIControlEventEditingDidEnd.
After the UITextField.text property is changed, any previous references to UITextPosition or UITextRange objects that were associated with the old text will be set to nil after you set the text property. You need to store what the text offset will be after the manipulation will be BEFORE you set the text property.
This worked for me (note, you do have to test whether cursorOffset is < textField.text.length if you remove any characters from t in the example below):
- (BOOL) textField:(UITextField *) textField shouldChangeCharactersInRange:(NSRange) range replacementString:(NSString *) string
{
UITextPosition *beginning = textField.beginningOfDocument;
UITextPosition *start = [textField positionFromPosition:beginning offset:range.location];
UITextPosition *end = [textField positionFromPosition:start offset:range.length];
UITextRange *textRange = [textField textRangeFromPosition:start toPosition:end];
// this will be the new cursor location after insert/paste/typing
NSInteger cursorOffset = [textField offsetFromPosition:beginning toPosition:start] + string.length;
// now apply the text changes that were typed or pasted in to the text field
[textField replaceRange:textRange withText:string];
// now go modify the text in interesting ways doing our post processing of what was typed...
NSMutableString *t = [textField.text mutableCopy];
t = [t upperCaseString];
// ... etc
// now update the text field and reposition the cursor afterwards
textField.text = t;
UITextPosition *newCursorPosition = [textField positionFromPosition:textField.beginningOfDocument offset:cursorOffset];
UITextRange *newSelectedRange = [textField textRangeFromPosition:newCursorPosition toPosition:newCursorPosition];
[textField setSelectedTextRange:newSelectedRange];
return NO;
}
And here is the swift version :
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let beginning = textField.beginningOfDocument
let start = textField.positionFromPosition(beginning, offset:range.location)
let end = textField.positionFromPosition(start!, offset:range.length)
let textRange = textField.textRangeFromPosition(start!, toPosition:end!)
let cursorOffset = textField.offsetFromPosition(beginning, toPosition:start!) + string.characters.count
// just used same text, use whatever you want :)
textField.text = (textField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string)
let newCursorPosition = textField.positionFromPosition(textField.beginningOfDocument, offset:cursorOffset)
let newSelectedRange = textField.textRangeFromPosition(newCursorPosition!, toPosition:newCursorPosition!)
textField.selectedTextRange = newSelectedRange
return false
}
Here is a swift 3 version
extension UITextField {
func setCursor(position: Int) {
let position = self.position(from: beginningOfDocument, offset: position)!
selectedTextRange = textRange(from: position, to: position)
}
}
What actually worked for me was very simple, just used dispatch main async:
DispatchQueue.main.async(execute: {
textField.selectedTextRange = textField.textRange(from: start, to: end)
})
Implement the code described in this answer: Moving the cursor to the beginning of UITextField
NSRange beginningRange = NSMakeRange(0, 0);
NSRange currentRange = [textField selectedRange];
if(!NSEqualRanges(beginningRange, currentRange))
{
[textField setSelectedRange:beginningRange];
}
EDIT: From this answer, it looks like you can just use this code with your UITextField if you're using iOS 5 or above. Otherwise, you need to use a UITextView instead.
Swift X.
To prevent from moving cursor from last position.
func textFieldDidChangeSelection(_ textField: UITextField) {
let point = CGPoint(x: bounds.maxX, y: bounds.height / 2)
if let textPosition = textField.closestPosition(to: point) {
textField.selectedTextRange = textField.textRange(from: textPosition, to: textPosition)
}
}