Move Cursor One Word at a Time in UTextView - iphone

I would like to create a button that moves the cursor position in a UITextView one word at a time. From a user perspective, this would be the same as Option-Right Arrow in Mac OS X, which is defined as "go to the word to the right of the insertion point."
I have found a couple ways to move on character at a time. How would you modify this to move one word at a time?
- (IBAction)rightArrowButtonPressed:(id)sender
{
myTextView.selectedRange = NSMakeRange(myTextView.selectedRange.location + 1, 0);
}
Thanks for any suggestions.

Was able to implement it like this,
- (IBAction)nextWord {
NSRange selectedRange = self.textView.selectedRange;
NSInteger currentLocation = selectedRange.location + selectedRange.length;
NSInteger textLength = [self.textView.text length];
if ( currentLocation == textLength ) {
return;
}
NSRange newRange = [self.textView.text rangeOfCharacterFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]
options:NSCaseInsensitiveSearch
range:NSMakeRange((currentLocation + 1), (textLength - 1 - currentLocation))];
if ( newRange.location != NSNotFound ) {
self.textView.selectedRange = NSMakeRange(newRange.location, 0);
} else {
self.textView.selectedRange = NSMakeRange(textLength, 0);
}
}
- (IBAction)previousWord {
NSRange selectedRange = self.textView.selectedRange;
NSInteger currentLocation = selectedRange.location;
if ( currentLocation == 0 ) {
return;
}
NSRange newRange = [self.textView.text rangeOfCharacterFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]
options:NSBackwardsSearch
range:NSMakeRange(0, (currentLocation - 1))];
if ( newRange.location != NSNotFound ) {
self.textView.selectedRange = NSMakeRange((newRange.location + 1), 0);
} else {
self.textView.selectedRange = NSMakeRange(0, 0);
}
}

Related

UISearch bar changes results after searching

i am implementing search bar in a tableview but after search my address is changed from actual address, what should i do now
-(void)searchBar:(UISearchBar*)searchBar textDidChange:(NSString*)text
{
if(text.length == 0)
{
isFiltered = FALSE;
}
else
{
isFiltered = true;
filteredListResults = [[NSMutableArray alloc] init];
GWTG *gwtg ;
for (gwtg in listResults)
{
NSRange nameRange = [gwtg.celebrity_name rangeOfString:text options:NSCaseInsensitiveSearch];//NSCaseInsensitiveSearch
NSRange descriptionRange = [gwtg.location_name rangeOfString:text options:NSCaseInsensitiveSearch];
if(nameRange.location != NSNotFound || descriptionRange.location != NSNotFound)
{
[filteredListResults addObject:gwtg];
}
}
}
[tblSearch reloadData];
}

Backspace many characters in a row in a UITextView

I want to check if a user backspaces a character in a textView if there are any of that same character connecting it for it to delete them all...
For example if the character I'm checking for is "e" I have the text "easy heeeeeello" and the user starts hitting backspace it will become:
easy heeeeeello -> easy heeeeeell -> easy heeeeeel -> easy heeeeee -> easy h
The code should detect that a backspace was pressed.
Then it will detect which text is going to be deleted, and if that text is a character (in our case "e") it will check if there are more "e"s touching that "e" creating a strand of "e"s and delete them all.
Can you help me?
OK, so I wrote this code, and it works for me
-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:#""]) {
//Detected backspace character as the new character is #"" meaning something will be deleted
char toDelete = [textView.text characterAtIndex:range.location];
int duplicateCharCount = 0;
for(int i =range.location-1; i>=0; i--) {
if([textView.text characterAtIndex:i] == toDelete) {
duplicateCharCount++;
} else {
break;
}
}
NSRange newRange = NSMakeRange(0, range.location - duplicateCharCount);
[textView setText:[textView.text substringWithRange:newRange]];
return NO;
} else {
return YES;
}
}
I know its not the best implementation, but now you know how to proceed
Hope this helps
Kind of fun, so I wrote the code just now.
The code works.
First, we should set the UITextView's delegate and respond to .
textView:shouldChangeTextInRange:replacementText:
According to the document,
If the user presses the Delete key, the length of the range is 1 and an empty string object replaces that single character.
So the code comes below :
#pragma mark -
#pragma mark - UITextView Delegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
static NSString *suffix = #"e";
if (range.length == 1 && [text length] == 0) {
// The user presses the Delete key.
NSString *currentText = [textView.text substringToIndex:range.location+1];
NSString *appendingText = [textView.text substringFromIndex:range.location+1];
if ([currentText hasSuffix:suffix]) {
NSRange range = [self inverseRangeOfString:currentText withSuffix:suffix];
currentText = [currentText stringByReplacingCharactersInRange:range withString:#""];
textView.text = [currentText stringByAppendingString:appendingText];
return NO;
}
}
return YES;
}
- (NSRange)inverseRangeOfString:(NSString *)str withSuffix:(NSString *)suffix
{
int length = [str length];
int lastIndex = length - 1;
int cnt = 0;
for (; lastIndex >= 0; --lastIndex) {
NSString *subStr = [str substringFromIndex:lastIndex];
if ([subStr hasPrefix:suffix]) {
cnt++;
} else {
break;
}
}
NSRange range = (NSRange){++lastIndex, cnt};
return range;
}

Getting NSRange of string between quotations?

I have a string:
He said "hello mate" yesterday.
I want to get an NSRange from the first quotation to the last quotation. So I tried something like this:
NSRange openingRange = [title rangeOfString:#"\""];
NSRange closingRange = [title rangeOfString:#"\""];
NSRange textRange = NSMakeRange(openingRange.location, closingRange.location+1 - openingRange.location);
But I'm not sure how to make it distinguish between the first quote and the second quote. How would I do this?
You could use a regular expression for this:
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"([\"])(?:\\\\\\1|.)*?\\1" options:0 error:&error];
NSRange range = [regex rangeOfFirstMatchInString:myString options:0 range:NSRangeMake(0, [myString length]];
Don't forget to check for errors ;)
You can always use 'rangeOfString:options:range:' for the second one (starting after the 'location' of the first one).
Option 1
- (NSRange)rangeOfQuoteInString:(NSString *)str {
int firstMatch = [str rangeOfString:#"\""].location;
int secondMatch = [str rangeOfString:#"\"" options:0 range:NSMakeRange(firstMatch + 1, [str length] - firstMatch - 1)].location;
return NSMakeRange(firstMatch, secondMatch + 1 - firstMatch);
}
I hope this is right. Done on my phone at dinner. ;-)
One other thing, though, since range of string likely does a similar implementation, why not iterate the 'char' values in the string and look for matches #1 & #2? Could be as fast or faster.
Option 2
- (NSRange)rangeOfQuoteInString:(NSString *)str {
int firstMatch = -1;
int secondMatch = -1;
for (int i = 0; i < [str length]; i = i + 1) {
unichar c = [str characterAtIndex:i];
if (c == '"') {
if (firstMatch == -1) {
firstMatch = i;
} else {
secondMatch = i;
break;
}
}
}
if (firstMatch == -1 || secondMatch == -1) {
// No full quote was found
return NSMakeRange(NSNotFound, 0);
} else {
return NSMakeRange(firstMatch, secondMatch + 1 - firstMatch);
}
}

Find all locations of substring in NSString (not just first)

There is a substring that occurs in a string several times. I use rangeOfString, but it seems that it can only find the first location. How can I find all the locations of the substring?
NSString *subString1 = #"</content>";
NSString *subString2 = #"--\n";
NSRange range1 = [newresults rangeOfString:subString1];
NSRange range2 = [newresults rangeOfString:subString2];
int location1 = range1.location;
int location2 = range2.location;
NSLog(#"%i",location1);
NSLog(#"%i",location2);
You can use rangeOfString:options:range: and set the third argument to be beyond the range of the first occurrence. For example, you can do something like this:
NSRange searchRange = NSMakeRange(0,string.length);
NSRange foundRange;
while (searchRange.location < string.length) {
searchRange.length = string.length-searchRange.location;
foundRange = [string rangeOfString:substring options:0 range:searchRange];
if (foundRange.location != NSNotFound) {
// found an occurrence of the substring! do stuff here
searchRange.location = foundRange.location+foundRange.length;
} else {
// no more substring to find
break;
}
}
Swift 3.0
Find all locations of substring i
let text = "This is the text and i want to replace something"
let mutableAttributedString = NSMutableAttributedString(string: text)
var searchRange = NSRange(location: 0, length: text.characters.count)
var foundRange = NSRange()
while searchRange.location < text.characters.count {
searchRange.length = text.characters.count - searchRange.location
foundRange = (text as NSString).range(of: "i", options: NSString.CompareOptions.caseInsensitive, range: searchRange)
if foundRange.location != NSNotFound {
// found an occurrence of the substring! do stuff here
searchRange.location = foundRange.location + foundRange.length
mutableAttributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.red, range: foundRange)
}
else {
// no more substring to find
break
}
}
//Apply
textLabel.attributedText = mutableAttributedString;
And this output-
This is my solution. Basically, the algorithm traverses the string looking for substring matches and returns those matches in an array.
Since an NSRange is a struct it cannot be added to the array directly. By using NSValue, I can encode the match first and then add it to the array. To retrieve the range, I then decode the NSValue object to an NSRange.
#import <Foundation/Foundation.h>
NSRange makeRangeFromIndex(NSUInteger index, NSUInteger length) {
return NSMakeRange(index, length - index);
}
NSArray<NSValue *> * allLocationsOfStringMatchingSubstring(NSString *text, NSString *pattern) {
NSMutableArray *matchingRanges = [NSMutableArray new];
NSUInteger textLength = text.length;
NSRange match = makeRangeFromIndex(0, textLength);
while(match.location != NSNotFound) {
match = [text rangeOfString:pattern options:0L range:match];
if (match.location != NSNotFound) {
NSValue *value = [NSValue value:&match withObjCType:#encode(NSRange)];
[matchingRanges addObject:value];
match = makeRangeFromIndex(match.location + 1, textLength);
}
}
return [matchingRanges copy];
}
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSString *text = #"TATACCATGGGCCATCATCATCATCATCATCATCATCATCATCACAG";
NSString *pattern = #"CAT";
NSArray<NSValue *> *matches = allLocationsOfStringMatchingSubstring(text, pattern);
NSLog(#"Text: %#", text);
NSLog(#"Pattern: %#", pattern);
NSLog(#"Number of matches found: %li", matches.count);
[matches enumerateObjectsUsingBlock:^(NSValue *obj, NSUInteger idx, BOOL *stop) {
NSRange match;
[obj getValue:&match];
NSLog(#" Match found at index: %li", match.location);
}];
}
return 0;
}
Passing nil to [string rangeOfString:substring options:nil range:searchRange]; shows a warning.
To get rid of the warning, put in an enum from this group
enum {
NSCaseInsensitiveSearch = 1,
NSLiteralSearch = 2,
NSBackwardsSearch = 4,
NSAnchoredSearch = 8,
NSNumericSearch = 64,
NSDiacriticInsensitiveSearch = 128,
NSWidthInsensitiveSearch = 256,
NSForcedOrderingSearch = 512,
NSRegularExpressionSearch = 1024
};
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/index.html#//apple_ref/doc/constant_group/Search_and_Comparison_Options
Here is a version in Swift 2.2 of PengOne's answer with input from kevinlawler and Gibtang
Note: string and substring are of type NSString
let fullStringLength = (string as String).characters.count
var searchRange = NSMakeRange(0, fullStringLength)
while searchRange.location < fullStringLength {
searchRange.length = fullStringLength - searchRange.location
let foundRange = string.rangeOfString(substring as String, options: .CaseInsensitiveSearch, range: searchRange)
if foundRange.location != NSNotFound {
// found an occurrence of the substring! do stuff here
searchRange.location = foundRange.location + 1
} else {
// no more strings to find
break
}
}
I suggest using regular expression because it's a more declarative way and has fewer lines of code to write.
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"%#" options:nil error:nil];
NSString *toSearchStr = #"12312 %# Text %# asdsa %#";
__block int occurs = 0;
[regex enumerateMatchesInString:toSearchStr options:0 range:NSMakeRange(0, toSearchStr.length) usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
occurs++;
}];
// occurs == 3

UILabel visible part of text

Is there a way to get the visible part of text in word wrapped UILabel? I mean exactly the last visible character?
I'd like to make two labels rounding the image and would like to continue the text which was out of rect for first label on the second one.
I know [NSString sizeWithFont...] but are there something reversing like [NSString stringVisibleInRect: withFont:...] ? :-)
Thank you in advance.
You could use a category to extend NSString and create the method you mention
#interface NSString (visibleText)
- (NSString*)stringVisibleInRect:(CGRect)rect withFont:(UIFont*)font;
#end
#implementation NSString (visibleText)
- (NSString*)stringVisibleInRect:(CGRect)rect withFont:(UIFont*)font
{
NSString *visibleString = #"";
for (int i = 1; i <= self.length; i++)
{
NSString *testString = [self substringToIndex:i];
CGSize stringSize = [testString sizeWithFont:font];
if (stringSize.height > rect.size.height || stringSize.width > rect.size.width)
break;
visibleString = testString;
}
return visibleString;
}
#end
Here's a O(log n) method with iOS 7 APIs. Only superficially tested, please comment if you find any bugs.
- (NSRange)hp_visibleRange
{
NSString *text = self.text;
NSRange visibleRange = NSMakeRange(NSNotFound, 0);
const NSInteger max = text.length - 1;
if (max >= 0)
{
NSInteger next = max;
const CGSize labelSize = self.bounds.size;
const CGSize maxSize = CGSizeMake(labelSize.width, CGFLOAT_MAX);
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = self.lineBreakMode;
NSDictionary * attributes = #{NSFontAttributeName:self.font, NSParagraphStyleAttributeName:paragraphStyle};
NSInteger right;
NSInteger best = 0;
do
{
right = next;
NSRange range = NSMakeRange(0, right + 1);
NSString *substring = [text substringWithRange:range];
CGSize textSize = [substring boundingRectWithSize:maxSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attributes
context:nil].size;
if (textSize.width <= labelSize.width && textSize.height <= labelSize.height)
{
visibleRange = range;
best = right;
next = right + (max - right) / 2;
} else if (right > 0)
{
next = right - (right - best) / 2;
}
} while (next != right);
}
return visibleRange;
}